Commit 4ea9b62b authored by Kurkó Mihály's avatar Kurkó Mihály Committed by Felix Lange

dashboard: send current block to the dashboard client (#19762)

This adds all dashboard changes from the last couple months.
We're about to remove the dashboard, but decided that we should
get all the recent work in first in case anyone wants to pick up this
project later on.

* cmd, dashboard, eth, p2p: send peer info to the dashboard
* dashboard: update npm packages, improve UI, rebase
* dashboard, p2p: remove println, change doc
* cmd, dashboard, eth, p2p: cleanup after review
* dashboard: send current block to the dashboard client
parent 6f1a600f
......@@ -156,9 +156,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
}
utils.RegisterEthService(stack, &cfg.Eth)
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
}
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
shhEnabled := enableWhisper(ctx)
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
......@@ -182,6 +179,12 @@ func makeFullNode(ctx *cli.Context) *node.Node {
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
}
// Add dashboard daemon if requested. This should be the last registered service
// in order to be able to collect information about the other services.
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
}
return stack
}
......
......@@ -1561,9 +1561,18 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
// RegisterDashboardService adds a dashboard to the stack.
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config, commit string) {
stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return dashboard.New(cfg, commit, ctx.ResolvePath("logs")), nil
err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var (
ethServ *eth.Ethereum
lesServ *les.LightEthereum
)
_ = ctx.Service(&ethServ)
_ = ctx.Service(&lesServ)
return dashboard.New(cfg, ethServ, lesServ, commit, ctx.ResolvePath("logs")), nil
})
if err != nil {
Fatalf("Failed to register the dashboard service: %v", err)
}
}
// RegisterShhService configures Whisper and adds it to the given node.
......
This diff is collapsed.
// @flow
// Copyright 2019 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/>.
import React, {Component} from 'react';
import type {Chain as ChainType} from '../types/content';
export const inserter = () => (update: ChainType, prev: ChainType) => {
if (!update.currentBlock) {
return;
}
if (!prev.currentBlock) {
prev.currentBlock = {};
}
prev.currentBlock.number = update.currentBlock.number;
prev.currentBlock.timestamp = update.currentBlock.timestamp;
return prev;
};
// styles contains the constant styles of the component.
const styles = {};
// themeStyles returns the styles generated from the theme for the component.
const themeStyles = theme => ({});
export type Props = {
content: Content,
};
type State = {};
// Logs renders the log page.
class Chain extends Component<Props, State> {
render() {
return <></>;
}
}
export default Chain;
......@@ -25,6 +25,7 @@ import Header from 'Header';
import Body from 'Body';
import {inserter as logInserter, SAME} from 'Logs';
import {inserter as peerInserter} from 'Network';
import {inserter as chainInserter} from 'Chain';
import {MENU} from '../common';
import type {Content} from '../types/content';
......@@ -83,17 +84,24 @@ const appender = <T>(limit: number, mapper = replacer) => (update: Array<T>, pre
// the execution of unnecessary operations (e.g. copy of the log array).
const defaultContent: () => Content = () => ({
general: {
version: null,
commit: null,
version: null,
genesis: '',
},
home: {},
chain: {
currentBlock: {
number: 0,
timestamp: 0,
},
},
home: {},
chain: {},
txpool: {},
network: {
peers: {
bundles: {},
},
diff: [],
diff: [],
activePeerCount: 0,
},
system: {
activeMemory: [],
......@@ -121,9 +129,10 @@ const updaters = {
general: {
version: replacer,
commit: replacer,
genesis: replacer,
},
home: null,
chain: null,
chain: chainInserter(),
txpool: null,
network: peerInserter(200),
system: {
......@@ -241,6 +250,7 @@ class Dashboard extends Component<Props, State> {
<div className={this.props.classes.dashboard} style={styles.dashboard}>
<Header
switchSideBar={this.switchSideBar}
content={this.state.content}
/>
<Body
opened={this.state.sideBar}
......
......@@ -32,6 +32,9 @@ import ChartRow from 'ChartRow';
import CustomTooltip, {bytePlotter, bytePerSecPlotter, percentPlotter, multiplier} from 'CustomTooltip';
import {chartStrokeWidth, styles as commonStyles} from '../common';
import type {General, System} from '../types/content';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faNetworkWired} from "@fortawesome/free-solid-svg-icons";
import Toolbar from "@material-ui/core/Toolbar";
const FOOTER_SYNC_ID = 'footerSyncId';
......@@ -154,6 +157,23 @@ class Footer extends Component<Props, State> {
render() {
const {general, system} = this.props;
let network = '';
switch (general.genesis) {
case '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3':
network = 'main';
break;
case '0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d':
network = 'ropsten';
break;
case '0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177':
network = 'rinkeby';
break;
case '0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a':
network = 'görli';
break;
default:
network = `unknown (${general.genesis.substring(0, 8)})`;
}
return (
<Grid container className={this.props.classes.footer} direction='row' alignItems='center' style={styles.footer}>
......@@ -202,6 +222,9 @@ class Footer extends Component<Props, State> {
</a>
</Typography>
)}
<Typography style={styles.headerText}>
<span style={commonStyles.light}>Network</span> {network}
</Typography>
</Grid>
</Grid>
);
......
......@@ -23,16 +23,25 @@ import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import IconButton from '@material-ui/core/IconButton';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faBars} from '@fortawesome/free-solid-svg-icons';
import {faBars, faSortAmountUp, faClock, faUsers, faSync} from '@fortawesome/free-solid-svg-icons';
import Typography from '@material-ui/core/Typography';
import type {Content} from '../types/content';
const magnitude = [31536000, 604800, 86400, 3600, 60, 1];
const label = ['y', 'w', 'd', 'h', 'm', 's'];
// styles contains the constant styles of the component.
const styles = {
header: {
height: '8%',
},
headerText: {
marginRight: 15,
},
toolbar: {
height: '100%',
height: '100%',
minHeight: 'unset',
},
};
......@@ -50,16 +59,52 @@ const themeStyles = (theme: Object) => ({
title: {
paddingLeft: theme.spacing.unit,
fontSize: 3 * theme.spacing.unit,
flex: 1,
},
});
export type Props = {
classes: Object, // injected by withStyles()
classes: Object, // injected by withStyles()
switchSideBar: () => void,
content: Content,
networkID: number,
};
type State = {
since: string,
}
// Header renders the header of the dashboard.
class Header extends Component<Props> {
class Header extends Component<Props, State> {
constructor(props) {
super(props);
this.state = {since: ''};
}
componentDidMount() {
this.interval = setInterval(() => this.setState(() => {
// time (seconds) since last block.
let timeDiff = Math.floor((Date.now() - this.props.content.chain.currentBlock.timestamp * 1000) / 1000);
let since = '';
let i = 0;
for (; i < magnitude.length && timeDiff < magnitude[i]; i++);
for (let j = 2; i < magnitude.length && j > 0; j--, i++) {
const t = Math.floor(timeDiff / magnitude[i]);
if (t > 0) {
since += `${t}${label[i]} `;
timeDiff %= magnitude[i];
}
}
if (since === '') {
since = 'now';
}
this.setState({since: since});
}), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const {classes} = this.props;
......@@ -72,6 +117,15 @@ class Header extends Component<Props> {
<Typography type='title' color='inherit' noWrap className={classes.title}>
Go Ethereum Dashboard
</Typography>
<Typography style={styles.headerText}>
<FontAwesomeIcon icon={faSortAmountUp} /> {this.props.content.chain.currentBlock.number}
</Typography>
<Typography style={styles.headerText}>
<FontAwesomeIcon icon={faClock} /> {this.state.since}
</Typography>
<Typography style={styles.headerText}>
<FontAwesomeIcon icon={faUsers} /> {this.props.content.network.activePeerCount}
</Typography>
</Toolbar>
</AppBar>
);
......
......@@ -20,6 +20,7 @@ import React, {Component} from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
import Chain from 'Chain';
import Network from 'Network';
import Logs from 'Logs';
import Footer from 'Footer';
......@@ -95,7 +96,9 @@ class Main extends Component<Props, State> {
children = <div>Work in progress.</div>;
break;
case MENU.get('chain').id:
children = <div>Work in progress.</div>;
children = <Chain
content={this.props.content.chain}
/>;
break;
case MENU.get('txpool').id:
children = <div>Work in progress.</div>;
......
This diff is collapsed.
......@@ -47,10 +47,11 @@ const themeStyles = theme => ({
background: theme.palette.grey[900],
},
listItem: {
minWidth: theme.spacing.unit * 7,
minWidth: theme.spacing(7),
color: theme.palette.common.white,
},
icon: {
fontSize: theme.spacing.unit * 3,
fontSize: theme.spacing(3),
overflow: 'unset',
},
});
......
{
"private": true,
"dependencies": {
"@babel/core": "7.3.4",
"@babel/plugin-proposal-class-properties": "7.3.4",
"@babel/core": "7.4.5",
"@babel/plugin-proposal-class-properties": "7.4.4",
"@babel/plugin-proposal-function-bind": "7.2.0",
"@babel/plugin-transform-flow-strip-types": "7.3.4",
"@babel/preset-env": "7.3.4",
"@babel/plugin-transform-flow-strip-types": "7.4.4",
"@babel/preset-env": "7.4.5",
"@babel/preset-react": "^7.0.0",
"@babel/preset-stage-0": "^7.0.0",
"@fortawesome/fontawesome-free-regular": "^5.0.13",
"@fortawesome/fontawesome-svg-core": "^1.2.15",
"@fortawesome/free-regular-svg-icons": "^5.7.2",
"@fortawesome/free-solid-svg-icons": "^5.7.2",
"@fortawesome/fontawesome-svg-core": "1.2.18",
"@fortawesome/free-regular-svg-icons": "5.8.2",
"@fortawesome/free-solid-svg-icons": "5.8.2",
"@fortawesome/react-fontawesome": "^0.1.4",
"@material-ui/core": "3.9.2",
"@material-ui/icons": "3.0.2",
"@material-ui/core": "4.0.1",
"@material-ui/icons": "4.0.1",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.5",
"babel-loader": "8.0.6",
"classnames": "^2.2.6",
"color-convert": "^2.0.0",
"css-loader": "2.1.1",
"escape-html": "^1.0.3",
"eslint": "5.15.1",
"eslint": "5.16.0",
"eslint-config-airbnb": "^17.0.0",
"eslint-loader": "2.1.2",
"eslint-plugin-flowtype": "3.4.2",
"eslint-plugin-import": "2.16.0",
"eslint-plugin-flowtype": "3.9.1",
"eslint-plugin-import": "2.17.3",
"eslint-plugin-jsx-a11y": "6.2.1",
"eslint-plugin-node": "8.0.1",
"eslint-plugin-promise": "4.0.1",
"eslint-plugin-react": "7.12.4",
"eslint-plugin-node": "9.1.0",
"eslint-plugin-promise": "4.1.1",
"eslint-plugin-react": "7.13.0",
"file-loader": "3.0.1",
"flow-bin": "0.94.0",
"flow-bin": "0.98.1",
"flow-bin-loader": "^1.0.3",
"flow-typed": "^2.5.1",
"js-beautify": "1.9.0",
"flow-typed": "2.5.2",
"js-beautify": "1.10.0",
"path": "^0.12.7",
"react": "16.8.4",
"react-dom": "16.8.4",
"react-hot-loader": "4.8.0",
"react-transition-group": "2.6.1",
"recharts": "1.5.0",
"react": "16.8.6",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "16.8.6",
"react-hot-loader": "4.8.8",
"react-scrollbar": "0.5.6",
"react-transition-group": "4.0.1",
"recharts": "1.6.2",
"style-loader": "0.23.1",
"terser-webpack-plugin": "^1.2.3",
"terser-webpack-plugin": "1.3.0",
"url": "^0.11.0",
"url-loader": "1.1.2",
"webpack": "4.29.6",
"webpack-cli": "3.2.3",
"webpack-dashboard": "3.0.0",
"webpack-dev-server": "3.2.1",
"webpack": "4.32.2",
"webpack-cli": "3.3.2",
"webpack-dashboard": "3.0.7",
"webpack-dev-server": "3.4.1",
"webpack-merge": "4.2.1"
},
"scripts": {
......
......@@ -35,6 +35,7 @@ export type ChartEntry = {
export type General = {
version: ?string,
commit: ?string,
genesis: ?string,
};
export type Home = {
......@@ -42,21 +43,29 @@ export type Home = {
};
export type Chain = {
/* TODO (kurkomisi) */
currentBlock: Block,
};
export type Block = {
number: number,
timestamp: number,
}
export type TxPool = {
/* TODO (kurkomisi) */
};
export type Network = {
peers: Peers,
diff: Array<PeerEvent>
peers: Peers,
diff: Array<PeerEvent>,
activePeerCount: number,
};
export type PeerEvent = {
ip: string,
id: string,
name: string,
addr: string,
enode: string,
protocols: {[string]: Object},
remove: string,
location: GeoLocation,
connected: Date,
......@@ -71,9 +80,9 @@ export type Peers = {
};
export type PeerBundle = {
location: GeoLocation,
knownPeers: {[string]: KnownPeer},
attempts: Array<UnknownPeer>,
location: GeoLocation,
knownPeers: {[string]: KnownPeer},
attempts: number,
};
export type KnownPeer = {
......@@ -81,14 +90,12 @@ export type KnownPeer = {
disconnected: Array<Date>,
ingress: Array<ChartEntries>,
egress: Array<ChartEntries>,
name: string,
enode: string,
protocols: {[string]: Object},
active: boolean,
};
export type UnknownPeer = {
connected: Date,
disconnected: Date,
};
export type GeoLocation = {
country: string,
city: string,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
package dashboard
import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
)
type block struct {
Number int64 `json:"number,omitempty"`
Time uint64 `json:"timestamp,omitempty"`
}
func (db *Dashboard) collectChainData() {
defer db.wg.Done()
var (
currentBlock *block
chainCh chan core.ChainHeadEvent
chainSub event.Subscription
)
switch {
case db.ethServ != nil:
chain := db.ethServ.BlockChain()
currentBlock = &block{
Number: chain.CurrentHeader().Number.Int64(),
Time: chain.CurrentHeader().Time,
}
chainCh = make(chan core.ChainHeadEvent)
chainSub = chain.SubscribeChainHeadEvent(chainCh)
case db.lesServ != nil:
chain := db.lesServ.BlockChain()
currentBlock = &block{
Number: chain.CurrentHeader().Number.Int64(),
Time: chain.CurrentHeader().Time,
}
chainCh = make(chan core.ChainHeadEvent)
chainSub = chain.SubscribeChainHeadEvent(chainCh)
default:
errc := <-db.quit
errc <- nil
return
}
defer chainSub.Unsubscribe()
db.chainLock.Lock()
db.history.Chain = &ChainMessage{
CurrentBlock: currentBlock,
}
db.chainLock.Unlock()
db.sendToAll(&Message{Chain: &ChainMessage{CurrentBlock: currentBlock}})
for {
select {
case e := <-chainCh:
currentBlock := &block{
Number: e.Block.Number().Int64(),
Time: e.Block.Time(),
}
db.chainLock.Lock()
db.history.Chain = &ChainMessage{
CurrentBlock: currentBlock,
}
db.chainLock.Unlock()
db.sendToAll(&Message{Chain: &ChainMessage{CurrentBlock: currentBlock}})
case err := <-chainSub.Err():
log.Warn("Chain subscription error", "err", err)
errc := <-db.quit
errc <- nil
return
case errc := <-db.quit:
errc <- nil
return
}
}
}
......@@ -27,14 +27,16 @@ package dashboard
import (
"fmt"
"io"
"net"
"net/http"
"sync"
"sync/atomic"
"time"
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
......@@ -44,7 +46,8 @@ import (
)
const (
sampleLimit = 200 // Maximum number of data samples
sampleLimit = 200 // Maximum number of data samples
dataCollectorCount = 4
)
// Dashboard contains the dashboard internals.
......@@ -57,16 +60,23 @@ type Dashboard struct {
history *Message // Stored historical data
lock sync.Mutex // Lock protecting the dashboard's internals
sysLock sync.RWMutex // Lock protecting the stored system data
peerLock sync.RWMutex // Lock protecting the stored peer data
logLock sync.RWMutex // Lock protecting the stored log data
lock sync.Mutex // Lock protecting the dashboard's internals
chainLock sync.RWMutex // Lock protecting the stored blockchain data
sysLock sync.RWMutex // Lock protecting the stored system data
peerLock sync.RWMutex // Lock protecting the stored peer data
logLock sync.RWMutex // Lock protecting the stored log data
geodb *geoDB // geoip database instance for IP to geographical information conversions
logdir string // Directory containing the log files
quit chan chan error // Channel used for graceful exit
wg sync.WaitGroup // Wait group used to close the data collector threads
peerCh chan p2p.MeteredPeerEvent // Peer event channel.
subPeer event.Subscription // Peer event subscription.
ethServ *eth.Ethereum // Ethereum object serving internals.
lesServ *les.LightEthereum // LightEthereum object serving internals.
}
// client represents active websocket connection with a remote browser.
......@@ -77,12 +87,23 @@ type client struct {
}
// New creates a new dashboard instance with the given configuration.
func New(config *Config, commit string, logdir string) *Dashboard {
now := time.Now()
func New(config *Config, ethServ *eth.Ethereum, lesServ *les.LightEthereum, commit string, logdir string) *Dashboard {
// There is a data race between the network layer and the dashboard, which
// can cause some lost peer events, therefore some peers might not appear
// on the dashboard.
// In order to solve this problem, the peer event subscription is registered
// here, before the network layer starts.
peerCh := make(chan p2p.MeteredPeerEvent, p2p.MeteredPeerLimit)
versionMeta := ""
if len(params.VersionMeta) > 0 {
versionMeta = fmt.Sprintf(" (%s)", params.VersionMeta)
}
var genesis common.Hash
if ethServ != nil {
genesis = ethServ.BlockChain().Genesis().Hash()
} else if lesServ != nil {
genesis = lesServ.BlockChain().Genesis().Hash()
}
return &Dashboard{
conns: make(map[uint32]*client),
config: config,
......@@ -91,24 +112,29 @@ func New(config *Config, commit string, logdir string) *Dashboard {
General: &GeneralMessage{
Commit: commit,
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
Genesis: genesis,
},
System: &SystemMessage{
ActiveMemory: emptyChartEntries(now, sampleLimit),
VirtualMemory: emptyChartEntries(now, sampleLimit),
NetworkIngress: emptyChartEntries(now, sampleLimit),
NetworkEgress: emptyChartEntries(now, sampleLimit),
ProcessCPU: emptyChartEntries(now, sampleLimit),
SystemCPU: emptyChartEntries(now, sampleLimit),
DiskRead: emptyChartEntries(now, sampleLimit),
DiskWrite: emptyChartEntries(now, sampleLimit),
ActiveMemory: emptyChartEntries(sampleLimit),
VirtualMemory: emptyChartEntries(sampleLimit),
NetworkIngress: emptyChartEntries(sampleLimit),
NetworkEgress: emptyChartEntries(sampleLimit),
ProcessCPU: emptyChartEntries(sampleLimit),
SystemCPU: emptyChartEntries(sampleLimit),
DiskRead: emptyChartEntries(sampleLimit),
DiskWrite: emptyChartEntries(sampleLimit),
},
},
logdir: logdir,
logdir: logdir,
peerCh: peerCh,
subPeer: p2p.SubscribeMeteredPeerEvent(peerCh),
ethServ: ethServ,
lesServ: lesServ,
}
}
// emptyChartEntries returns a ChartEntry array containing limit number of empty samples.
func emptyChartEntries(t time.Time, limit int) ChartEntries {
func emptyChartEntries(limit int) ChartEntries {
ce := make(ChartEntries, limit)
for i := 0; i < limit; i++ {
ce[i] = new(ChartEntry)
......@@ -127,7 +153,8 @@ func (db *Dashboard) APIs() []rpc.API { return nil }
func (db *Dashboard) Start(server *p2p.Server) error {
log.Info("Starting dashboard", "url", fmt.Sprintf("http://%s:%d", db.config.Host, db.config.Port))
db.wg.Add(3)
db.wg.Add(dataCollectorCount)
go db.collectChainData()
go db.collectSystemData()
go db.streamLogs()
go db.collectPeerData()
......@@ -141,7 +168,11 @@ func (db *Dashboard) Start(server *p2p.Server) error {
}
db.listener = listener
go http.Serve(listener, nil)
go func() {
if err := http.Serve(listener, nil); err != http.ErrServerClosed {
log.Warn("Could not accept incoming HTTP connections", "err", err)
}
}()
return nil
}
......@@ -155,8 +186,8 @@ func (db *Dashboard) Stop() error {
errs = append(errs, err)
}
// Close the collectors.
errc := make(chan error, 1)
for i := 0; i < 3; i++ {
errc := make(chan error, dataCollectorCount)
for i := 0; i < dataCollectorCount; i++ {
db.quit <- errc
if err := <-errc; err != nil {
errs = append(errs, err)
......@@ -230,20 +261,21 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
}()
// Send the past data.
db.chainLock.RLock()
db.sysLock.RLock()
db.peerLock.RLock()
db.logLock.RLock()
h := deepcopy.Copy(db.history).(*Message)
db.chainLock.RUnlock()
db.sysLock.RUnlock()
db.peerLock.RUnlock()
db.logLock.RUnlock()
client.msg <- h
// Start tracking the connection and drop at connection loss.
db.lock.Lock()
client.msg <- h
db.conns[id] = client
db.lock.Unlock()
defer func() {
......
......@@ -18,6 +18,8 @@ package dashboard
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common"
)
type Message struct {
......@@ -37,8 +39,9 @@ type ChartEntry struct {
}
type GeneralMessage struct {
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
Genesis common.Hash `json:"genesis,omitempty"`
}
type HomeMessage struct {
......@@ -46,7 +49,7 @@ type HomeMessage struct {
}
type ChainMessage struct {
/* TODO (kurkomisi) */
CurrentBlock *block `json:"currentBlock,omitempty"`
}
type TxPoolMessage struct {
......
This diff is collapsed.
......@@ -304,7 +304,7 @@ func (t *dialTask) dial(srv *Server, dest *enode.Node) error {
if err != nil {
return &dialError{err}
}
mfd := newMeteredConn(fd, false, dest.IP())
mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()})
return srv.SetupConn(mfd, t.flags, dest)
}
......
......@@ -19,7 +19,6 @@
package p2p
import (
"fmt"
"net"
"sync"
"sync/atomic"
......@@ -28,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p/enode"
)
const (
......@@ -58,24 +56,24 @@ var (
type MeteredPeerEventType int
const (
// PeerConnected is the type of event emitted when a peer successfully
// made the handshake.
PeerConnected MeteredPeerEventType = iota
// PeerDisconnected is the type of event emitted when a peer disconnects.
PeerDisconnected
// PeerHandshakeSucceeded is the type of event
// emitted when a peer successfully makes the handshake.
PeerHandshakeSucceeded MeteredPeerEventType = iota
// PeerHandshakeFailed is the type of event emitted when a peer fails to
// make the handshake or disconnects before the handshake.
// make the handshake or disconnects before it.
PeerHandshakeFailed
// PeerDisconnected is the type of event emitted when a peer disconnects.
PeerDisconnected
)
// MeteredPeerEvent is an event emitted when peers connect or disconnect.
type MeteredPeerEvent struct {
Type MeteredPeerEventType // Type of peer event
IP net.IP // IP address of the peer
ID enode.ID // NodeID of the peer
Addr string // TCP address of the peer
Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection
Peer *Peer // Connected remote node instance
Ingress uint64 // Ingress count at the moment of the event
Egress uint64 // Egress count at the moment of the event
}
......@@ -91,9 +89,9 @@ func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription {
type meteredConn struct {
net.Conn // Network connection to wrap with metering
connected time.Time // Connection time of the peer
ip net.IP // IP address of the peer
id enode.ID // NodeID of the peer
connected time.Time // Connection time of the peer
addr *net.TCPAddr // TCP address of the peer
peer *Peer // Peer instance
// trafficMetered denotes if the peer is registered in the traffic registries.
// Its value is true if the metered peer count doesn't reach the limit in the
......@@ -109,13 +107,13 @@ type meteredConn struct {
// connection meter and also increases the metered peer count. If the metrics
// system is disabled or the IP address is unspecified, this function returns
// the original object.
func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn {
func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
// Short circuit if metrics are disabled
if !metrics.Enabled {
return conn
}
if ip.IsUnspecified() {
log.Warn("Peer IP is unspecified")
if addr == nil || addr.IP.IsUnspecified() {
log.Warn("Peer address is unspecified")
return conn
}
// Bump the connection counters and wrap the connection
......@@ -128,7 +126,7 @@ func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn {
return &meteredConn{
Conn: conn,
ip: ip,
addr: addr,
connected: time.Now(),
}
}
......@@ -159,30 +157,27 @@ func (c *meteredConn) Write(b []byte) (n int, err error) {
return n, err
}
// handshakeDone is called when a peer handshake is done. Registers the peer to
// the ingress and the egress traffic registries using the peer's IP and node ID,
// also emits connect event.
func (c *meteredConn) handshakeDone(id enode.ID) {
// TODO (kurkomisi): use the node URL instead of the pure node ID. (the String() method of *Node)
// handshakeDone is called after the connection passes the handshake.
func (c *meteredConn) handshakeDone(peer *Peer) {
if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
// Don't register the peer in the traffic registries.
atomic.AddInt32(&meteredPeerCount, -1)
c.lock.Lock()
c.id, c.trafficMetered = id, false
c.peer, c.trafficMetered = peer, false
c.lock.Unlock()
log.Warn("Metered peer count reached the limit")
} else {
key := fmt.Sprintf("%s/%s", c.ip, id.String())
enode := peer.Node().String()
c.lock.Lock()
c.id, c.trafficMetered = id, true
c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry)
c.egressMeter = metrics.NewRegisteredMeter(key, PeerEgressRegistry)
c.peer, c.trafficMetered = peer, true
c.ingressMeter = metrics.NewRegisteredMeter(enode, PeerIngressRegistry)
c.egressMeter = metrics.NewRegisteredMeter(enode, PeerEgressRegistry)
c.lock.Unlock()
}
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerConnected,
IP: c.ip,
ID: id,
Type: PeerHandshakeSucceeded,
Addr: c.addr.String(),
Peer: peer,
Elapsed: time.Since(c.connected),
})
}
......@@ -192,44 +187,43 @@ func (c *meteredConn) handshakeDone(id enode.ID) {
func (c *meteredConn) Close() error {
err := c.Conn.Close()
c.lock.RLock()
if c.id == (enode.ID{}) {
// If the peer disconnects before the handshake.
if c.peer == nil {
// If the peer disconnects before/during the handshake.
c.lock.RUnlock()
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerHandshakeFailed,
IP: c.ip,
Addr: c.addr.String(),
Elapsed: time.Since(c.connected),
})
activePeerGauge.Dec(1)
return err
}
id := c.id
peer := c.peer
if !c.trafficMetered {
// If the peer isn't registered in the traffic registries.
c.lock.RUnlock()
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerDisconnected,
IP: c.ip,
ID: id,
Addr: c.addr.String(),
Peer: peer,
})
activePeerGauge.Dec(1)
return err
}
ingress, egress := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count())
ingress, egress, enode := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()), c.peer.Node().String()
c.lock.RUnlock()
// Decrement the metered peer count
atomic.AddInt32(&meteredPeerCount, -1)
// Unregister the peer from the traffic registries
key := fmt.Sprintf("%s/%s", c.ip, id)
PeerIngressRegistry.Unregister(key)
PeerEgressRegistry.Unregister(key)
PeerIngressRegistry.Unregister(enode)
PeerEgressRegistry.Unregister(enode)
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerDisconnected,
IP: c.ip,
ID: id,
Addr: c.addr.String(),
Peer: peer,
Ingress: ingress,
Egress: egress,
})
......
......@@ -779,6 +779,9 @@ running:
if p.Inbound() {
inboundCount++
}
if conn, ok := c.fd.(*meteredConn); ok {
conn.handshakeDone(p)
}
}
// The dialer logic relies on the assumption that
// dial tasks complete after the peer has been added or
......@@ -902,9 +905,13 @@ func (srv *Server) listenLoop() {
continue
}
if remoteIP != nil {
fd = newMeteredConn(fd, true, remoteIP)
var addr *net.TCPAddr
if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok {
addr = tcp
}
fd = newMeteredConn(fd, true, addr)
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
}
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
go func() {
srv.SetupConn(fd, inboundConn, nil)
slots <- struct{}{}
......@@ -974,9 +981,6 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
} else {
c.node = nodeFromConn(remotePubkey, c.fd)
}
if conn, ok := c.fd.(*meteredConn); ok {
conn.handshakeDone(c.node.ID())
}
clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags)
err = srv.checkpoint(c, srv.checkpointPostHandshake)
if err != nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment