mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
cmd, dashboard: work in progress dashboard prototype
This commit is contained in:
parent
382c9266e6
commit
4c31d4b296
7 changed files with 1148 additions and 7 deletions
|
|
@ -30,6 +30,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/contracts/release"
|
"github.com/ethereum/go-ethereum/contracts/release"
|
||||||
|
"github.com/ethereum/go-ethereum/dashboard"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -76,10 +77,11 @@ type ethstatsConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type gethConfig struct {
|
type gethConfig struct {
|
||||||
Eth eth.Config
|
Eth eth.Config
|
||||||
Shh whisper.Config
|
Shh whisper.Config
|
||||||
Node node.Config
|
Node node.Config
|
||||||
Ethstats ethstatsConfig
|
Ethstats ethstatsConfig
|
||||||
|
Dashboard dashboard.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(file string, cfg *gethConfig) error {
|
func loadConfig(file string, cfg *gethConfig) error {
|
||||||
|
|
@ -107,12 +109,17 @@ func defaultNodeConfig() node.Config {
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func defaultDashboardConfig() dashboard.Config {
|
||||||
|
return dashboard.DefaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||||
// Load defaults.
|
// Load defaults.
|
||||||
cfg := gethConfig{
|
cfg := gethConfig{
|
||||||
Eth: eth.DefaultConfig,
|
Eth: eth.DefaultConfig,
|
||||||
Shh: whisper.DefaultConfig,
|
Shh: whisper.DefaultConfig,
|
||||||
Node: defaultNodeConfig(),
|
Node: defaultNodeConfig(),
|
||||||
|
Dashboard: defaultDashboardConfig(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load config file.
|
// Load config file.
|
||||||
|
|
@ -135,6 +142,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||||
|
|
||||||
utils.SetShhConfig(ctx, stack, &cfg.Shh)
|
utils.SetShhConfig(ctx, stack, &cfg.Shh)
|
||||||
|
|
||||||
|
utils.SetDashboardConfig(ctx, &cfg.Dashboard)
|
||||||
return stack, cfg
|
return stack, cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,6 +161,10 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
|
|
||||||
utils.RegisterEthService(stack, &cfg.Eth)
|
utils.RegisterEthService(stack, &cfg.Eth)
|
||||||
|
|
||||||
|
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||||
|
utils.RegisterDashboardService(stack, &cfg.Dashboard)
|
||||||
|
}
|
||||||
|
|
||||||
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||||
shhEnabled := enableWhisper(ctx)
|
shhEnabled := enableWhisper(ctx)
|
||||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ var (
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
utils.KeyStoreDirFlag,
|
||||||
utils.NoUSBFlag,
|
utils.NoUSBFlag,
|
||||||
|
utils.DashboardEnabledFlag,
|
||||||
|
utils.DashboardAddrFlag,
|
||||||
|
utils.DashboardPortFlag,
|
||||||
|
utils.DashboardRefreshFlag,
|
||||||
|
utils.DashboardAssetsFlag,
|
||||||
utils.EthashCacheDirFlag,
|
utils.EthashCacheDirFlag,
|
||||||
utils.EthashCachesInMemoryFlag,
|
utils.EthashCachesInMemoryFlag,
|
||||||
utils.EthashCachesOnDiskFlag,
|
utils.EthashCachesOnDiskFlag,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/dashboard"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
|
|
@ -177,6 +178,31 @@ var (
|
||||||
Name: "lightkdf",
|
Name: "lightkdf",
|
||||||
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
|
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
|
||||||
}
|
}
|
||||||
|
// Dashboard settings
|
||||||
|
DashboardEnabledFlag = cli.BoolFlag{
|
||||||
|
Name: "dashboard",
|
||||||
|
Usage: "Enable the dashboard",
|
||||||
|
}
|
||||||
|
DashboardAddrFlag = cli.StringFlag{
|
||||||
|
Name: "dashboard.addr",
|
||||||
|
Usage: "Dashboard listening interface",
|
||||||
|
Value: dashboard.DefaultConfig.Host,
|
||||||
|
}
|
||||||
|
DashboardPortFlag = cli.IntFlag{
|
||||||
|
Name: "dashboard.host",
|
||||||
|
Usage: "Dashboard listening port",
|
||||||
|
Value: dashboard.DefaultConfig.Port,
|
||||||
|
}
|
||||||
|
DashboardRefreshFlag = cli.DurationFlag{
|
||||||
|
Name: "dashboard.refresh",
|
||||||
|
Usage: "Dashboard refresh rate",
|
||||||
|
Value: dashboard.DefaultConfig.Refresh,
|
||||||
|
}
|
||||||
|
DashboardAssetsFlag = cli.StringFlag{
|
||||||
|
Name: "dashboard.assets",
|
||||||
|
Usage: "Directory of the dashboard assets, useful for debugging (default = assets.go binary)",
|
||||||
|
Value: dashboard.DefaultConfig.Assets,
|
||||||
|
}
|
||||||
// Ethash settings
|
// Ethash settings
|
||||||
EthashCacheDirFlag = DirectoryFlag{
|
EthashCacheDirFlag = DirectoryFlag{
|
||||||
Name: "ethash.cachedir",
|
Name: "ethash.cachedir",
|
||||||
|
|
@ -997,6 +1023,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDashboardConfig applies dashboard related command line flags to the config.
|
||||||
|
func SetDashboardConfig(ctx *cli.Context, cfg *dashboard.Config) {
|
||||||
|
cfg.Host = ctx.GlobalString(DashboardAddrFlag.Name)
|
||||||
|
cfg.Port = ctx.GlobalInt(DashboardPortFlag.Name)
|
||||||
|
cfg.Refresh = ctx.GlobalDuration(DashboardRefreshFlag.Name)
|
||||||
|
cfg.Assets = ctx.GlobalString(DashboardAssetsFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterEthService adds an Ethereum client to the stack.
|
// RegisterEthService adds an Ethereum client to the stack.
|
||||||
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -1019,6 +1053,13 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterDashboardService adds a dashboard to the stack.
|
||||||
|
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config) {
|
||||||
|
stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
|
return dashboard.NewDashboard(cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterShhService configures Whisper and adds it to the given node.
|
// RegisterShhService configures Whisper and adds it to the given node.
|
||||||
func RegisterShhService(stack *node.Node, cfg *whisper.Config) {
|
func RegisterShhService(stack *node.Node, cfg *whisper.Config) {
|
||||||
if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
|
if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
|
||||||
|
|
|
||||||
235
dashboard/assets.go
Normal file
235
dashboard/assets.go
Normal file
File diff suppressed because one or more lines are too long
525
dashboard/assets/dashboard.html
Normal file
525
dashboard/assets/dashboard.html
Normal file
File diff suppressed because one or more lines are too long
42
dashboard/config.go
Normal file
42
dashboard/config.go
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DefaultConfig contains default settings for the dashboard.
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
Host: "localhost",
|
||||||
|
Port: 8080,
|
||||||
|
Refresh: time.Second,
|
||||||
|
Assets: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
//Config is the config 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.
|
||||||
|
Host string `toml:",omitempty"`
|
||||||
|
|
||||||
|
// Port is the TCP port number on which to start the dashboard server. The
|
||||||
|
// default zero value is/ valid and will pick a port number randomly (useful
|
||||||
|
// for ephemeral nodes).
|
||||||
|
Port int `toml:",omitempty"`
|
||||||
|
|
||||||
|
Refresh time.Duration
|
||||||
|
Assets string
|
||||||
|
}
|
||||||
281
dashboard/dashboard.go
Normal file
281
dashboard/dashboard.go
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
procSampleLimit = 200
|
||||||
|
memSampleLimit = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nextId uint32 = 0 // Next connection id
|
||||||
|
)
|
||||||
|
|
||||||
|
type dashboard struct {
|
||||||
|
config *Config
|
||||||
|
|
||||||
|
listener net.Listener
|
||||||
|
index []byte // Index page to serve up on the web
|
||||||
|
conns []*client // Currently live websocket connections
|
||||||
|
|
||||||
|
mtrcs *mtrcs `json:",omitempty"`
|
||||||
|
stats *status `json:",omitempty"`
|
||||||
|
|
||||||
|
lock sync.RWMutex // Lock protecting the dashboard's internals
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
conn *websocket.Conn // Particular live websocket connection
|
||||||
|
logger log.Logger // Logger for the particular live websocket connection
|
||||||
|
}
|
||||||
|
|
||||||
|
type mtrcs struct {
|
||||||
|
Processor []*data `json:"proc,omitempty"`
|
||||||
|
Memory []*data `json:"mem,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type data struct {
|
||||||
|
T int `json:"time,omitempty"`
|
||||||
|
Value float64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type status struct {
|
||||||
|
Peers int
|
||||||
|
Block int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDashboard(config *Config) (*dashboard, error) {
|
||||||
|
log.Trace("NewDashboard() called")
|
||||||
|
|
||||||
|
dashboard := &dashboard{
|
||||||
|
config: config,
|
||||||
|
mtrcs: &mtrcs{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Assets == "" {
|
||||||
|
tmpl, err := Asset("dashboard.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
website := new(bytes.Buffer)
|
||||||
|
if err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
|
||||||
|
"procSampleLimit": procSampleLimit,
|
||||||
|
"memSampleLimit": memSampleLimit,
|
||||||
|
}); err != nil {
|
||||||
|
log.Crit("Failed to render the dashboard template", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboard.index = website.Bytes()
|
||||||
|
return dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO case: DashboardAssetsFlag is set
|
||||||
|
//dashboard.index = ioutil.ReadFile()
|
||||||
|
return dashboard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dashboard) Protocols() []p2p.Protocol { return nil }
|
||||||
|
|
||||||
|
func (db *dashboard) APIs() []rpc.API { return nil }
|
||||||
|
|
||||||
|
func (db *dashboard) Start(server *p2p.Server) error {
|
||||||
|
log.Trace("Start() called", "config", db.config)
|
||||||
|
|
||||||
|
go db.collectData()
|
||||||
|
|
||||||
|
http.HandleFunc("/", db.webHandler)
|
||||||
|
http.Handle("/api", websocket.Handler(db.apiHandler))
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", db.config.Host, db.config.Port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db.listener = listener
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Trace("Starting server...")
|
||||||
|
|
||||||
|
if err := http.Serve(listener, nil); err != nil {
|
||||||
|
log.Warn("Server failed", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dashboard) Stop() error {
|
||||||
|
log.Trace("Terminating dashboard...")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
db.lock.Lock()
|
||||||
|
if err = db.listener.Close(); err != nil {
|
||||||
|
log.Warn("Failed to close listener", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range db.conns {
|
||||||
|
if err := c.conn.Close(); err != nil {
|
||||||
|
c.logger.Warn("Failed to close connection", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
|
||||||
|
//TODO not only index
|
||||||
|
w.Write(db.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiHandler handles requests for dashboard
|
||||||
|
func (db *dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
|
log.Trace("apiHandler() called")
|
||||||
|
|
||||||
|
client := &client{
|
||||||
|
conn: conn,
|
||||||
|
logger: log.New("id", atomic.AddUint32(&nextId, 1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start tracking the connection and drop at connection loss
|
||||||
|
db.lock.Lock()
|
||||||
|
db.conns = append(db.conns, client)
|
||||||
|
db.lock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
client.logger.Trace("Connection interrupted")
|
||||||
|
|
||||||
|
db.lock.Lock()
|
||||||
|
for i, c := range db.conns {
|
||||||
|
if c.conn == client.conn {
|
||||||
|
if err := c.conn.Close(); err != nil {
|
||||||
|
c.logger.Warn("Failed to close connection", "err", err)
|
||||||
|
}
|
||||||
|
db.conns = append(db.conns[:i], db.conns[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.lock.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
db.sendHistory(client)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var msg struct {
|
||||||
|
text string `json:"text"`
|
||||||
|
}
|
||||||
|
if err := websocket.JSON.Receive(conn, &msg); err != nil {
|
||||||
|
client.logger.Warn("Receive failed", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ignore any message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectData collects the required data to plot on the dashboard
|
||||||
|
func (db *dashboard) collectData() {
|
||||||
|
log.Trace("collectData() called")
|
||||||
|
|
||||||
|
for {
|
||||||
|
now := time.Now().Second()
|
||||||
|
traffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
||||||
|
traffic = traffic * traffic
|
||||||
|
//if traffic != 0 {
|
||||||
|
// traffic = math.Log(traffic)
|
||||||
|
//}
|
||||||
|
memInuse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
|
||||||
|
//if memInuse != 0 {
|
||||||
|
// memInuse = math.Log(memInuse)
|
||||||
|
//}
|
||||||
|
traff := &data{
|
||||||
|
T: now,
|
||||||
|
Value: traffic,
|
||||||
|
}
|
||||||
|
mem := &data{
|
||||||
|
T: now,
|
||||||
|
Value: memInuse,
|
||||||
|
}
|
||||||
|
|
||||||
|
db.update(traff, mem)
|
||||||
|
|
||||||
|
time.Sleep(db.config.Refresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update updates the dashboards through the live websocket connections
|
||||||
|
func (db *dashboard) update(proc *data, mem *data) {
|
||||||
|
//log.Trace("update() called")
|
||||||
|
|
||||||
|
// if the samples' # exceeds the limit, just remove the first element
|
||||||
|
first := 0
|
||||||
|
if len(db.mtrcs.Processor) == procSampleLimit {
|
||||||
|
first = 1
|
||||||
|
}
|
||||||
|
db.mtrcs.Processor = append(db.mtrcs.Processor[first:], proc)
|
||||||
|
|
||||||
|
first = 0
|
||||||
|
if len(db.mtrcs.Memory) == memSampleLimit {
|
||||||
|
first = 1
|
||||||
|
}
|
||||||
|
db.mtrcs.Memory = append(db.mtrcs.Memory[first:], mem)
|
||||||
|
|
||||||
|
for _, c := range db.conns {
|
||||||
|
//c.logger.Trace("Updating dashboard...")
|
||||||
|
|
||||||
|
msg := &map[string]interface{}{
|
||||||
|
"proc": proc,
|
||||||
|
"mem": mem,
|
||||||
|
}
|
||||||
|
if err := websocket.JSON.Send(c.conn, msg); err != nil {
|
||||||
|
c.logger.Warn("Failed to update dashboard", "msg", msg, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dashboard) sendHistory(c *client) {
|
||||||
|
c.logger.Trace("Sending history...")
|
||||||
|
msg := &map[string]interface{}{
|
||||||
|
"mtrcs": db.mtrcs,
|
||||||
|
}
|
||||||
|
if err := websocket.JSON.Send(c.conn, msg); err != nil {
|
||||||
|
c.logger.Warn("Failed to send history", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue