mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 02:42:27 +00:00
dashboard, p2p: separate active peers from inactive ones
This commit is contained in:
parent
92d5937023
commit
547bad0d60
12 changed files with 13589 additions and 13946 deletions
24611
dashboard/assets.go
24611
dashboard/assets.go
File diff suppressed because one or more lines are too long
|
|
@ -23,6 +23,7 @@ import TableHead from '@material-ui/core/TableHead';
|
||||||
import TableBody from '@material-ui/core/TableBody';
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
import TableCell from '@material-ui/core/TableCell';
|
import TableCell from '@material-ui/core/TableCell';
|
||||||
|
import Grid from '@material-ui/core/Grid/Grid';
|
||||||
import AreaChart from 'recharts/es6/chart/AreaChart';
|
import AreaChart from 'recharts/es6/chart/AreaChart';
|
||||||
import Tooltip from 'recharts/es6/component/Tooltip';
|
import Tooltip from 'recharts/es6/component/Tooltip';
|
||||||
import Area from 'recharts/es6/cartesian/Area';
|
import Area from 'recharts/es6/cartesian/Area';
|
||||||
|
|
@ -57,13 +58,13 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
|
||||||
delete bundle.knownPeers[event.id];
|
delete bundle.knownPeers[event.id];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'unknown': {
|
case 'attempt': {
|
||||||
const bundle = prev.peers.bundles[event.ip];
|
const bundle = prev.peers.bundles[event.ip];
|
||||||
if (!bundle || !Array.isArray(bundle.unknownPeers) || bundle.unknownPeers.length < 1) {
|
if (!bundle || !Array.isArray(bundle.attempts) || bundle.attempts.length < 1) {
|
||||||
console.error('No unknown peer to remove', event.ip);
|
console.error('No unknown peer to remove', event.ip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bundle.unknownPeers.splice(0, 1);
|
bundle.attempts.splice(0, 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +77,7 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
|
||||||
longitude: 0,
|
longitude: 0,
|
||||||
},
|
},
|
||||||
knownPeers: {},
|
knownPeers: {},
|
||||||
unknownPeers: [],
|
attempts: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const bundle = prev.peers.bundles[event.ip];
|
const bundle = prev.peers.bundles[event.ip];
|
||||||
|
|
@ -85,13 +86,13 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!event.id) {
|
if (!event.id) {
|
||||||
bundle.unknownPeers.push({
|
bundle.attempts.push({
|
||||||
connected: event.connected,
|
connected: event.connected,
|
||||||
disconnected: event.disconnected,
|
disconnected: event.disconnected,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!bundle.knownPeers[event.id]) {
|
if (!bundle.knownPeers || !bundle.knownPeers[event.id]) {
|
||||||
bundle.knownPeers[event.id] = {
|
bundle.knownPeers[event.id] = {
|
||||||
connected: [],
|
connected: [],
|
||||||
disconnected: [],
|
disconnected: [],
|
||||||
|
|
@ -138,7 +139,7 @@ type State = {};
|
||||||
|
|
||||||
// Network renders the network page.
|
// Network renders the network page.
|
||||||
class Network extends Component<Props, State> {
|
class Network extends Component<Props, State> {
|
||||||
formatTime = (t) => {
|
formatTime = (t: string) => {
|
||||||
const time = new Date(t);
|
const time = new Date(t);
|
||||||
if (isNaN(time)) {
|
if (isNaN(time)) {
|
||||||
return '';
|
return '';
|
||||||
|
|
@ -153,16 +154,14 @@ class Network extends Component<Props, State> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Grid container direction='row' justify='space-between' spacing={24}>
|
||||||
|
<Grid item xs={6}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>IP</TableCell>
|
|
||||||
<TableCell>Location</TableCell>
|
|
||||||
<TableCell>Node ID</TableCell>
|
<TableCell>Node ID</TableCell>
|
||||||
|
<TableCell>Location</TableCell>
|
||||||
<TableCell>Traffic</TableCell>
|
<TableCell>Traffic</TableCell>
|
||||||
<TableCell>Connected</TableCell>
|
|
||||||
<TableCell>Disconnected</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
@ -171,22 +170,21 @@ class Network extends Component<Props, State> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TableRow key={`known${ip}`}>
|
<TableRow key={`known_${ip}`}>
|
||||||
<TableCell>{ip}</TableCell>
|
<TableCell>
|
||||||
|
{Object.keys(bundle.knownPeers).map(id => id.substring(0, 10)).join(' ')}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{bundle.location ? (() => {
|
{bundle.location ? (() => {
|
||||||
const l = bundle.location;
|
const l = bundle.location;
|
||||||
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''} ${l.latitude} ${l.longitude}`;
|
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
|
||||||
})() : ''}
|
})() : ''}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
{Object.keys(bundle.knownPeers).map(id => id.substring(0, 10)).join(' ')}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{Object.values(bundle.knownPeers).map(({ingress, egress}) => (
|
{Object.values(bundle.knownPeers).map(({ingress, egress}) => (
|
||||||
<div>
|
<div>
|
||||||
<AreaChart
|
<AreaChart
|
||||||
width={300} height={50}
|
width={200} height={18}
|
||||||
syncId={'footerSyncId'}
|
syncId={'footerSyncId'}
|
||||||
data={egress.map(({value}) => ({egress: value || 0}))}
|
data={egress.map(({value}) => ({egress: value || 0}))}
|
||||||
margin={{top: 5, right: 5, bottom: 0, left: 5}}
|
margin={{top: 5, right: 5, bottom: 0, left: 5}}
|
||||||
|
|
@ -195,7 +193,7 @@ class Network extends Component<Props, State> {
|
||||||
<Area isAnimationActive={false} type='monotone' dataKey='egress' stroke='#8884d8' fill='#8884d8' />
|
<Area isAnimationActive={false} type='monotone' dataKey='egress' stroke='#8884d8' fill='#8884d8' />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
<AreaChart
|
<AreaChart
|
||||||
width={300} height={50}
|
width={200} height={18}
|
||||||
syncId={'footerSyncId'}
|
syncId={'footerSyncId'}
|
||||||
data={ingress.map(({value}) => ({ingress: -value || 0}))}
|
data={ingress.map(({value}) => ({ingress: -value || 0}))}
|
||||||
margin={{top: 0, right: 5, bottom: 5, left: 5}}
|
margin={{top: 0, right: 5, bottom: 5, left: 5}}
|
||||||
|
|
@ -206,52 +204,46 @@ class Network extends Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
{Object.values(bundle.knownPeers).map(peer => peer.connected && peer.connected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{Object.values(bundle.knownPeers).map(peer => peer.disconnected && peer.disconnected.map(time => this.formatTime(time)).join(' ')).join(', ')}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>IP</TableCell>
|
<TableCell>IP</TableCell>
|
||||||
<TableCell>Location</TableCell>
|
<TableCell>Location</TableCell>
|
||||||
<TableCell>Connected</TableCell>
|
<TableCell>Attempts</TableCell>
|
||||||
<TableCell>Disconnected</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
|
{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
|
||||||
if (!bundle.unknownPeers || bundle.unknownPeers.length < 1) {
|
if (!bundle.attempts || bundle.attempts.length < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TableRow key={`unknown${ip}`}>
|
<TableRow key={`attempt_${ip}`}>
|
||||||
<TableCell>{ip}</TableCell>
|
<TableCell>{ip}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{bundle.location ? (() => {
|
{bundle.location ? (() => {
|
||||||
const l = bundle.location;
|
const l = bundle.location;
|
||||||
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''} ${l.latitude} ${l.longitude}`;
|
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
|
||||||
})() : ''}
|
})() : ''}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{Object.values(bundle.unknownPeers).map(peer => peer.connected && `${this.formatTime(peer.connected)}`).join(', ')}
|
{Object.values(bundle.attempts).length}
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{Object.values(bundle.unknownPeers).map(peer => peer.disconnected && `${this.formatTime(peer.disconnected)}`).join(', ')}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>);
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
|
||||||
import Dashboard from './components/Dashboard';
|
import Dashboard from './components/Dashboard';
|
||||||
|
|
||||||
const theme: Object = createMuiTheme({
|
const theme: Object = createMuiTheme({
|
||||||
|
// typography: {
|
||||||
|
// useNextVariants: true,
|
||||||
|
// },
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "7.1.2",
|
"@babel/core": "^7.2.2",
|
||||||
"@babel/plugin-proposal-class-properties": "7.1.0",
|
"@babel/plugin-proposal-class-properties": "7.2.3",
|
||||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
"@babel/plugin-proposal-function-bind": "7.2.0",
|
||||||
"@babel/plugin-transform-flow-strip-types": "^7.0.0",
|
"@babel/plugin-transform-flow-strip-types": "7.2.3",
|
||||||
"@babel/preset-env": "7.1.0",
|
"@babel/preset-env": "7.2.3",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@babel/preset-stage-0": "^7.0.0",
|
"@babel/preset-stage-0": "^7.0.0",
|
||||||
"@material-ui/core": "3.2.0",
|
"@material-ui/core": "3.8.1",
|
||||||
"@material-ui/icons": "^3.0.1",
|
"@material-ui/icons": "^3.0.1",
|
||||||
"babel-eslint": "10.0.1",
|
"babel-eslint": "10.0.1",
|
||||||
"babel-loader": "8.0.4",
|
"babel-loader": "8.0.5",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "2.1.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"eslint": "5.7.0",
|
"eslint": "5.11.1",
|
||||||
"eslint-config-airbnb": "^17.0.0",
|
"eslint-config-airbnb": "^17.0.0",
|
||||||
"eslint-loader": "2.1.1",
|
"eslint-loader": "2.1.1",
|
||||||
"eslint-plugin-flowtype": "3.0.0",
|
"eslint-plugin-flowtype": "3.2.0",
|
||||||
"eslint-plugin-import": "^2.13.0",
|
"eslint-plugin-import": "^2.13.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.1.2",
|
"eslint-plugin-jsx-a11y": "6.1.2",
|
||||||
"eslint-plugin-node": "^7.0.1",
|
"eslint-plugin-node": "8.0.0",
|
||||||
"eslint-plugin-promise": "4.0.1",
|
"eslint-plugin-promise": "4.0.1",
|
||||||
"eslint-plugin-react": "7.11.1",
|
"eslint-plugin-react": "7.12.2",
|
||||||
"file-loader": "2.0.0",
|
"file-loader": "3.0.1",
|
||||||
"flow-bin": "0.83.0",
|
"flow-bin": "0.89.0",
|
||||||
"flow-bin-loader": "^1.0.3",
|
"flow-bin-loader": "^1.0.3",
|
||||||
"flow-typed": "^2.5.1",
|
"flow-typed": "^2.5.1",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"react": "16.5.2",
|
"react": "16.7.0",
|
||||||
"react-dom": "16.5.2",
|
"react-dom": "16.7.0",
|
||||||
"react-fa": "^5.0.0",
|
"react-fa": "^5.0.0",
|
||||||
"react-hot-loader": "4.3.11",
|
"react-hot-loader": "4.6.3",
|
||||||
"react-transition-group": "2.5.0",
|
"react-transition-group": "2.5.2",
|
||||||
"recharts": "1.3.4",
|
"recharts": "1.4.2",
|
||||||
"style-loader": "0.23.1",
|
"style-loader": "0.23.1",
|
||||||
"uglifyjs-webpack-plugin": "2.0.1",
|
"uglifyjs-webpack-plugin": "2.1.1",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"url-loader": "1.1.2",
|
"url-loader": "1.1.2",
|
||||||
"webpack": "4.20.2",
|
"webpack": "4.28.3",
|
||||||
"webpack-cli": "3.1.2",
|
"webpack-cli": "3.2.0",
|
||||||
"webpack-dev-server": "3.1.9",
|
"webpack-dev-server": "3.1.14",
|
||||||
"webpack-merge": "^4.1.4"
|
"webpack-merge": "4.1.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.prod.js",
|
"build": "webpack --config webpack.config.prod.js",
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ export type Content = {
|
||||||
export type ChartEntries = Array<ChartEntry>;
|
export type ChartEntries = Array<ChartEntry>;
|
||||||
|
|
||||||
export type ChartEntry = {
|
export type ChartEntry = {
|
||||||
time: Date,
|
|
||||||
value: number,
|
value: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,7 +72,7 @@ export type Peers = {
|
||||||
export type PeerBundle = {
|
export type PeerBundle = {
|
||||||
location: GeoLocation,
|
location: GeoLocation,
|
||||||
knownPeers: {[string]: KnownPeer},
|
knownPeers: {[string]: KnownPeer},
|
||||||
unknownPeers: Array<UnknownPeer>,
|
attempts: Array<UnknownPeer>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type KnownPeer = {
|
export type KnownPeer = {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -60,7 +60,7 @@ type Dashboard struct {
|
||||||
peerLock sync.RWMutex // Lock protecting the stored peer data
|
peerLock sync.RWMutex // Lock protecting the stored peer data
|
||||||
logLock sync.RWMutex // Lock protecting the stored log data
|
logLock sync.RWMutex // Lock protecting the stored log data
|
||||||
|
|
||||||
geodb *GeoDB // geoip database instance for IP to geographical information conversions
|
geodb *geoDB // geoip database instance for IP to geographical information conversions
|
||||||
logdir string // Directory containing the log files
|
logdir string // Directory containing the log files
|
||||||
|
|
||||||
quit chan chan error // Channel used for graceful exit
|
quit chan chan error // Channel used for graceful exit
|
||||||
|
|
@ -91,14 +91,14 @@ func New(config *Config, commit string, logdir string) *Dashboard {
|
||||||
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
|
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
|
||||||
},
|
},
|
||||||
System: &SystemMessage{
|
System: &SystemMessage{
|
||||||
ActiveMemory: emptyChartEntries(now, sampleLimit, config.Refresh),
|
ActiveMemory: emptyChartEntries(now, sampleLimit),
|
||||||
VirtualMemory: emptyChartEntries(now, sampleLimit, config.Refresh),
|
VirtualMemory: emptyChartEntries(now, sampleLimit),
|
||||||
NetworkIngress: emptyChartEntries(now, sampleLimit, config.Refresh),
|
NetworkIngress: emptyChartEntries(now, sampleLimit),
|
||||||
NetworkEgress: emptyChartEntries(now, sampleLimit, config.Refresh),
|
NetworkEgress: emptyChartEntries(now, sampleLimit),
|
||||||
ProcessCPU: emptyChartEntries(now, sampleLimit, config.Refresh),
|
ProcessCPU: emptyChartEntries(now, sampleLimit),
|
||||||
SystemCPU: emptyChartEntries(now, sampleLimit, config.Refresh),
|
SystemCPU: emptyChartEntries(now, sampleLimit),
|
||||||
DiskRead: emptyChartEntries(now, sampleLimit, config.Refresh),
|
DiskRead: emptyChartEntries(now, sampleLimit),
|
||||||
DiskWrite: emptyChartEntries(now, sampleLimit, config.Refresh),
|
DiskWrite: emptyChartEntries(now, sampleLimit),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logdir: logdir,
|
logdir: logdir,
|
||||||
|
|
@ -106,12 +106,10 @@ func New(config *Config, commit string, logdir string) *Dashboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
// emptyChartEntries returns a ChartEntry array containing limit number of empty samples.
|
// emptyChartEntries returns a ChartEntry array containing limit number of empty samples.
|
||||||
func emptyChartEntries(t time.Time, limit int, refresh time.Duration) ChartEntries {
|
func emptyChartEntries(t time.Time, limit int) ChartEntries {
|
||||||
ce := make(ChartEntries, limit)
|
ce := make(ChartEntries, limit)
|
||||||
for i := 0; i < limit; i++ {
|
for i := 0; i < limit; i++ {
|
||||||
ce[i] = &ChartEntry{
|
ce[i] = new(ChartEntry)
|
||||||
Time: t.Add(-time.Duration(i) * refresh),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ce
|
return ce
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import (
|
||||||
"github.com/apilayer/freegeoip"
|
"github.com/apilayer/freegeoip"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GeoDBInfo contains all the geographical information we could extract based on an IP
|
// geoDBInfo contains all the geographical information we could extract based on an IP
|
||||||
// address.
|
// address.
|
||||||
type GeoDBInfo struct {
|
type geoDBInfo struct {
|
||||||
Country struct {
|
Country struct {
|
||||||
Names struct {
|
Names struct {
|
||||||
English string `maxminddb:"en" json:"en,omitempty"`
|
English string `maxminddb:"en" json:"en,omitempty"`
|
||||||
|
|
@ -42,22 +42,22 @@ type GeoDBInfo struct {
|
||||||
} `maxminddb:"location" json:"location,omitempty"`
|
} `maxminddb:"location" json:"location,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoLocation contains geographical information.
|
// geoLocation contains geographical information.
|
||||||
type GeoLocation struct {
|
type geoLocation struct {
|
||||||
Country string `json:"country,omitempty"`
|
Country string `json:"country,omitempty"`
|
||||||
City string `json:"city,omitempty"`
|
City string `json:"city,omitempty"`
|
||||||
Latitude float64 `json:"latitude,omitempty"`
|
Latitude float64 `json:"latitude,omitempty"`
|
||||||
Longitude float64 `json:"longitude,omitempty"`
|
Longitude float64 `json:"longitude,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoDB represents a geoip database that can be queried for IP to geographical
|
// geoDB represents a geoip database that can be queried for IP to geographical
|
||||||
// information conversions.
|
// information conversions.
|
||||||
type GeoDB struct {
|
type geoDB struct {
|
||||||
geodb *freegeoip.DB
|
geodb *freegeoip.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open creates a new geoip database with an up-to-date database from the internet.
|
// Open creates a new geoip database with an up-to-date database from the internet.
|
||||||
func OpenGeoDB() (*GeoDB, error) {
|
func openGeoDB() (*geoDB, error) {
|
||||||
// Initiate a geoip database to cross reference locations
|
// Initiate a geoip database to cross reference locations
|
||||||
db, err := freegeoip.OpenURL(freegeoip.MaxMindDB, 24*time.Hour, time.Hour)
|
db, err := freegeoip.OpenURL(freegeoip.MaxMindDB, 24*time.Hour, time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -70,26 +70,26 @@ func OpenGeoDB() (*GeoDB, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Assemble and return our custom wrapper
|
// Assemble and return our custom wrapper
|
||||||
return &GeoDB{geodb: db}, nil
|
return &geoDB{geodb: db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close terminates the database background updater.
|
// Close terminates the database background updater.
|
||||||
func (db *GeoDB) Close() error {
|
func (db *geoDB) close() error {
|
||||||
db.geodb.Close()
|
db.geodb.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup converts an IP address to a geographical location.
|
// Lookup converts an IP address to a geographical location.
|
||||||
func (db *GeoDB) Lookup(ip net.IP) *GeoDBInfo {
|
func (db *geoDB) lookup(ip net.IP) *geoDBInfo {
|
||||||
result := new(GeoDBInfo)
|
result := new(geoDBInfo)
|
||||||
db.geodb.Lookup(ip, result)
|
db.geodb.Lookup(ip, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location retrieves the geographical location of the given IP address.
|
// Location retrieves the geographical location of the given IP address.
|
||||||
func (db *GeoDB) Location(ip string) *GeoLocation {
|
func (db *geoDB) location(ip string) *geoLocation {
|
||||||
location := db.Lookup(net.ParseIP(ip))
|
location := db.lookup(net.ParseIP(ip))
|
||||||
return &GeoLocation{
|
return &geoLocation{
|
||||||
Country: location.Country.Names.English,
|
Country: location.Country.Names.English,
|
||||||
City: location.City.Names.English,
|
City: location.City.Names.English,
|
||||||
Latitude: location.Location.Latitude,
|
Latitude: location.Location.Latitude,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package dashboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
|
@ -34,7 +33,6 @@ type Message struct {
|
||||||
type ChartEntries []*ChartEntry
|
type ChartEntries []*ChartEntry
|
||||||
|
|
||||||
type ChartEntry struct {
|
type ChartEntry struct {
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,8 +56,8 @@ type TxPoolMessage struct {
|
||||||
// NetworkMessage contains information about the peers
|
// NetworkMessage contains information about the peers
|
||||||
// organized based on their IP address and node ID.
|
// organized based on their IP address and node ID.
|
||||||
type NetworkMessage struct {
|
type NetworkMessage struct {
|
||||||
Peers *PeerContainer `json:"peers,omitempty"` // Peer tree.
|
Peers *peerContainer `json:"peers,omitempty"` // Peer tree.
|
||||||
Diff []*PeerEvent `json:"diff,omitempty"` // Events that change the peer tree.
|
Diff []*peerEvent `json:"diff,omitempty"` // Events that change the peer tree.
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemMessage contains the metered system data samples.
|
// SystemMessage contains the metered system data samples.
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
eventBufferLimit = 128 // Maximum number of events of buffered peer events.
|
eventBufferLimit = 128 // Maximum number of buffered peer events.
|
||||||
knownPeerLimit = 30 //p2p.MeteredPeerLimit // Maximum number of stored peers, which successfully made the handshake.
|
knownPeerLimit = 100 // Maximum number of stored peers, which successfully made the handshake.
|
||||||
unknownPeerLimit = 100 //p2p.MeteredPeerLimit // Maximum number of stored peers, which failed to make the handshake.
|
attemptLimit = 100 // Maximum number of stored peers, which failed to make the handshake.
|
||||||
|
|
||||||
// eventLimit is the maximum number of the dashboard's custom peer events,
|
// eventLimit is the maximum number of the dashboard's custom peer events,
|
||||||
// that are collected between two metering period and sent to the clients
|
// that are collected between two metering period and sent to the clients
|
||||||
|
|
@ -39,16 +39,17 @@ const (
|
||||||
eventLimit = knownPeerLimit << 2
|
eventLimit = knownPeerLimit << 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerContainer contains information about the node's peers. This data structure
|
// peerContainer contains information about the node's peers. This data structure
|
||||||
// maintains the metered peer data based on the different behaviours of the peers.
|
// maintains the metered peer data based on the different behaviours of the peers.
|
||||||
//
|
//
|
||||||
// Every peer has an IP address, and the peers that manage to make the handshake
|
// Every peer has an IP address, and the peers that manage to make the handshake
|
||||||
// (known peers) have node IDs too. There can appear more peers with the same IP,
|
// (known peers) have node IDs too. There can appear more peers with the same IP,
|
||||||
// therefore the peer maintainer data structure is a tree consisting of a map of
|
// therefore the peer container data structure is a tree consisting of a map of
|
||||||
// maps, where the first key groups the peers by IP, while the second one groups
|
// maps, where the first key groups the peers by IP, while the second one groups
|
||||||
// them by the node ID. The peers failing before the handshake (unknown peers)
|
// them by the node ID. The known peers can be active if their connection is still
|
||||||
// only have IP addresses, so their connection attempts are stored as part of the
|
// open, or inactive otherwise. The peers failing before the handshake (unknown
|
||||||
// value of the outer map.
|
// peers) only have IP addresses, so their connection attempts are stored as part
|
||||||
|
// of the value of the outer map.
|
||||||
//
|
//
|
||||||
// Another criteria is to limit the number of metered peers so that
|
// Another criteria is to limit the number of metered peers so that
|
||||||
// they don't fill the memory. The selection order is based on the
|
// they don't fill the memory. The selection order is based on the
|
||||||
|
|
@ -61,12 +62,9 @@ const (
|
||||||
// the limit, the first element is removed from the list, as well as from
|
// the limit, the first element is removed from the list, as well as from
|
||||||
// the tree.
|
// the tree.
|
||||||
//
|
//
|
||||||
// The active peers that are still connected have priority over the disconnected
|
// The active peers have priority over the inactive ones, therefore
|
||||||
// ones, therefore the list is extended by a separator, which is a pointer to a
|
// they have their own list. The separation makes it sure that the
|
||||||
// list element. The separator separates the active peers from the inactive ones,
|
// inactive peers are always removed before the active ones.
|
||||||
// and it is the entry for the list. If the peer that is to be inserted is active,
|
|
||||||
// it goes after the separator, otherwise it goes before. This way the active peers
|
|
||||||
// never move to the front before the inactive ones.
|
|
||||||
//
|
//
|
||||||
// The peers that don't manage to make handshake are not inserted into the list,
|
// The peers that don't manage to make handshake are not inserted into the list,
|
||||||
// only their connection attempts are appended to the array belonging to their IP.
|
// only their connection attempts are appended to the array belonging to their IP.
|
||||||
|
|
@ -76,116 +74,99 @@ const (
|
||||||
//
|
//
|
||||||
// This data structure makes it possible to marshal the peer
|
// This data structure makes it possible to marshal the peer
|
||||||
// history simply by passing it to the JSON marshaler.
|
// history simply by passing it to the JSON marshaler.
|
||||||
type PeerContainer struct {
|
type peerContainer struct {
|
||||||
// Bundles is the outer map using the peer's IP address as key.
|
// Bundles is the outer map using the peer's IP address as key.
|
||||||
Bundles map[string]*PeerBundle `json:"bundles,omitempty"`
|
Bundles map[string]*peerBundle `json:"bundles,omitempty"`
|
||||||
|
|
||||||
// activeSeparator is a pointer to the last inactive peer element, splitting
|
// activePeers contains the peers with opened connection in random order.
|
||||||
// the list into an inactive and an active part, and forming the entry for
|
activePeers *list.List
|
||||||
// the peer list.
|
|
||||||
activeSeparator *list.Element
|
|
||||||
|
|
||||||
// knownPeers contains the peers that managed to make handshake.
|
// inactivePeers contains the peers with closed connection in chronological order.
|
||||||
knownPeers *list.List
|
inactivePeers *list.List
|
||||||
|
|
||||||
// unknownPeers is the super array containing the IP addresses, from which
|
// attemptOrder is the super array containing the IP addresses, from which
|
||||||
// the peers attempted to connect then failed before/during the handshake.
|
// the peers attempted to connect then failed before/during the handshake.
|
||||||
// Its values are appended in chronological order, which means that the
|
// Its values are appended in chronological order, which means that the
|
||||||
// oldest attempt is at the beginning of the array. When the first element
|
// oldest attempt is at the beginning of the array. When the first element
|
||||||
// is removed, the first element of the related bundle's attempt array is
|
// is removed, the first element of the related bundle's attempt array is
|
||||||
// removed too, ensuring that always the latest attempts are stored.
|
// removed too, ensuring that always the latest attempts are stored.
|
||||||
unknownPeers []string
|
attemptOrder []string
|
||||||
|
|
||||||
// geodb is the geoip database used to retrieve the peers' geographical location.
|
// geodb is the geoip database used to retrieve the peers' geographical location.
|
||||||
geodb *GeoDB
|
geodb *geoDB
|
||||||
|
|
||||||
// refresh is the refresh rate used to generate the
|
|
||||||
// initial auxiliary traffic samples' time stamps.
|
|
||||||
refresh time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPeerContainer returns a new instance of the peer container.
|
// newPeerContainer returns a new instance of the peer container.
|
||||||
func NewPeerContainer(geodb *GeoDB, refresh time.Duration) *PeerContainer {
|
func newPeerContainer(geodb *geoDB) *peerContainer {
|
||||||
return &PeerContainer{
|
return &peerContainer{
|
||||||
Bundles: make(map[string]*PeerBundle),
|
Bundles: make(map[string]*peerBundle),
|
||||||
knownPeers: list.New(),
|
activePeers: list.New(),
|
||||||
unknownPeers: make([]string, 0, unknownPeerLimit),
|
inactivePeers: list.New(),
|
||||||
|
attemptOrder: make([]string, 0, attemptLimit),
|
||||||
geodb: geodb,
|
geodb: geodb,
|
||||||
refresh: refresh,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrInitBundle inserts a new peer bundle into the map, if the peer belonging
|
// bundle inserts a new peer bundle into the map, if the peer belonging
|
||||||
// to the given IP wasn't metered so far. In this case retrieves the location of
|
// to the given IP wasn't metered so far. In this case retrieves the location of
|
||||||
// the IP address from the database and creates a corresponding peer event.
|
// the IP address from the database and creates a corresponding peer event.
|
||||||
// Returns the bundle belonging to the given IP and the events occurring during
|
// Returns the bundle belonging to the given IP and the events occurring during
|
||||||
// the initialization.
|
// the initialization.
|
||||||
func (pc *PeerContainer) getOrInitBundle(ip string) (*PeerBundle, []*PeerEvent) {
|
func (pc *peerContainer) bundle(ip string) (*peerBundle, []*peerEvent) {
|
||||||
var events []*PeerEvent
|
var events []*peerEvent
|
||||||
if _, ok := pc.Bundles[ip]; !ok {
|
if _, ok := pc.Bundles[ip]; !ok {
|
||||||
location := pc.geodb.Location(ip)
|
location := pc.geodb.location(ip)
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
Location: location,
|
Location: location,
|
||||||
})
|
})
|
||||||
pc.Bundles[ip] = &PeerBundle{
|
pc.Bundles[ip] = &peerBundle{
|
||||||
Location: location,
|
Location: location,
|
||||||
KnownPeers: make(map[string]*KnownPeer),
|
KnownPeers: make(map[string]*knownPeer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pc.Bundles[ip], events
|
return pc.Bundles[ip], events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PeerContainer) getOrInitKnownPeer(ip, id string) (*KnownPeer, []*PeerEvent) {
|
|
||||||
bundle, events := pc.getOrInitBundle(ip)
|
|
||||||
peer, peerEvents := bundle.getOrInitKnownPeer(ip, id, pc.refresh)
|
|
||||||
return peer, append(events, peerEvents...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extendKnown handles the events of the successfully connected peers.
|
// extendKnown handles the events of the successfully connected peers.
|
||||||
// Returns the events occurring during the extension.
|
// Returns the events occurring during the extension.
|
||||||
func (pc *PeerContainer) extendKnown(event *PeerEvent) []*PeerEvent {
|
func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
|
||||||
peer, events := pc.getOrInitKnownPeer(event.IP, event.ID)
|
bundle, events := pc.bundle(event.IP)
|
||||||
|
peer, peerEvents := bundle.knownPeer(event.IP, event.ID)
|
||||||
|
events = append(events, peerEvents...)
|
||||||
// Append the connect and the disconnect events to
|
// Append the connect and the disconnect events to
|
||||||
// the corresponding arrays keeping the limit.
|
// the corresponding arrays keeping the limit.
|
||||||
if event.Connected != nil {
|
switch {
|
||||||
|
case event.Connected != nil:
|
||||||
peer.Connected = append(peer.Connected, event.Connected)
|
peer.Connected = append(peer.Connected, event.Connected)
|
||||||
if first := len(peer.Connected) - sampleLimit; first > 0 {
|
if first := len(peer.Connected) - sampleLimit; first > 0 {
|
||||||
peer.Connected = peer.Connected[first:]
|
peer.Connected = peer.Connected[first:]
|
||||||
}
|
}
|
||||||
}
|
case event.Disconnected != nil:
|
||||||
if event.Disconnected != nil {
|
|
||||||
peer.Disconnected = append(peer.Disconnected, event.Disconnected)
|
peer.Disconnected = append(peer.Disconnected, event.Disconnected)
|
||||||
if first := len(peer.Disconnected) - sampleLimit; first > 0 {
|
if first := len(peer.Disconnected) - sampleLimit; first > 0 {
|
||||||
peer.Disconnected = peer.Disconnected[first:]
|
peer.Disconnected = peer.Disconnected[first:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if peer.listElement != nil {
|
if peer.listElement != nil {
|
||||||
if pc.activeSeparator == peer.listElement {
|
|
||||||
pc.activeSeparator = pc.activeSeparator.Prev()
|
|
||||||
}
|
|
||||||
// If the peer is already in the list, remove and reinsert it.
|
// If the peer is already in the list, remove and reinsert it.
|
||||||
_ = pc.knownPeers.Remove(peer.listElement)
|
_ = pc.activePeers.Remove(peer.listElement)
|
||||||
|
_ = pc.inactivePeers.Remove(peer.listElement)
|
||||||
peer.listElement = nil
|
peer.listElement = nil
|
||||||
}
|
}
|
||||||
// Insert the peer into the list.
|
// Insert the peer into the list.
|
||||||
if pc.activeSeparator == nil {
|
peer.listElement = pc.activePeers.PushBack(peer)
|
||||||
// If there isn't active peer in the list
|
for pc.activePeers.Len()+pc.inactivePeers.Len() > knownPeerLimit {
|
||||||
peer.listElement = pc.knownPeers.PushBack(peer)
|
// While the count of the known peers is greater than the limit,
|
||||||
pc.activeSeparator = peer.listElement
|
// remove the first element from the inactive peer list and from the map.
|
||||||
} else if e := pc.knownPeers.InsertAfter(peer, pc.activeSeparator); e != nil {
|
if removedPeer, ok := pc.inactivePeers.Remove(pc.inactivePeers.Front()).(*knownPeer); ok {
|
||||||
// Insert the peer after the last inactive peer, and set it as the separator.
|
events = append(events, pc.removeKnown(removedPeer.ip, removedPeer.id)...)
|
||||||
peer.listElement, pc.activeSeparator = e, e
|
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Failed to insert known peer", "peer", *peer)
|
log.Warn("Failed to parse the removed peer")
|
||||||
}
|
}
|
||||||
for pc.knownPeers.Len() > knownPeerLimit {
|
|
||||||
// While the length of the list is greater than the limit,
|
|
||||||
// remove the first element from the list and from the map.
|
|
||||||
if pc.activeSeparator == pc.knownPeers.Front() {
|
|
||||||
pc.activeSeparator = nil
|
|
||||||
}
|
}
|
||||||
if removedPeer, ok := pc.knownPeers.Remove(pc.knownPeers.Front()).(*KnownPeer); ok {
|
for pc.activePeers.Len() > knownPeerLimit {
|
||||||
|
if removedPeer, ok := pc.activePeers.Remove(pc.activePeers.Front()).(*knownPeer); ok {
|
||||||
events = append(events, pc.removeKnown(removedPeer.ip, removedPeer.id)...)
|
events = append(events, pc.removeKnown(removedPeer.ip, removedPeer.id)...)
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Failed to parse the removed peer")
|
log.Warn("Failed to parse the removed peer")
|
||||||
|
|
@ -194,140 +175,125 @@ func (pc *PeerContainer) extendKnown(event *PeerEvent) []*PeerEvent {
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// extendUnknown handles the events of the peers failing before/during the handshake.
|
// handleAttempt handles the events of the peers failing before/during the handshake.
|
||||||
// Returns the events occurring during the extension.
|
// Returns the events occurring during the extension.
|
||||||
func (pc *PeerContainer) extendUnknown(event *PeerEvent) []*PeerEvent {
|
func (pc *peerContainer) handleAttempt(event *peerEvent) []*peerEvent {
|
||||||
bundle, events := pc.getOrInitBundle(event.IP)
|
bundle, events := pc.bundle(event.IP)
|
||||||
bundle.UnknownPeers = append(bundle.UnknownPeers, &UnknownPeer{
|
bundle.Attempts = append(bundle.Attempts, &peerAttempt{
|
||||||
Connected: *event.Connected,
|
Connected: *event.Connected,
|
||||||
Disconnected: *event.Disconnected,
|
Disconnected: *event.Disconnected,
|
||||||
})
|
})
|
||||||
pc.unknownPeers = append(pc.unknownPeers, event.IP)
|
pc.attemptOrder = append(pc.attemptOrder, event.IP)
|
||||||
for len(pc.unknownPeers) > unknownPeerLimit {
|
for len(pc.attemptOrder) > attemptLimit {
|
||||||
// While the length of the connection attempt order array is greater
|
// While the length of the connection attempt order array is greater
|
||||||
// than the limit, remove the first element from the involved peer's
|
// than the limit, remove the first element from the involved peer's
|
||||||
// array and also from the super array.
|
// array and also from the super array.
|
||||||
events = append(events, pc.removeUnknown(pc.unknownPeers[0])...)
|
events = append(events, pc.removeAttempt(pc.attemptOrder[0])...)
|
||||||
pc.unknownPeers = pc.unknownPeers[1:]
|
pc.attemptOrder = pc.attemptOrder[1:]
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// setActive moves the peer denoted by the given IP address and node ID after
|
// setActive pushes the peer denoted by the given IP address and node ID
|
||||||
// the list's active separator. Takes no effect if the peer doesn't exist.
|
// into the active peer list. Takes no effect if the peer doesn't exist.
|
||||||
func (pc *PeerContainer) setActive(ip, id string) {
|
func (pc *peerContainer) setActive(ip, id string) {
|
||||||
if bundle, ok := pc.Bundles[ip]; ok {
|
if bundle, ok := pc.Bundles[ip]; ok {
|
||||||
if peer, ok := bundle.KnownPeers[id]; ok {
|
if peer, ok := bundle.KnownPeers[id]; ok {
|
||||||
if peer.listElement != nil {
|
if peer.listElement != nil {
|
||||||
// If the peer is already in the list, remove it first.
|
_ = pc.inactivePeers.Remove(peer.listElement)
|
||||||
// Theoretically this should always happen, because all
|
_ = pc.activePeers.Remove(peer.listElement)
|
||||||
// the peers are inserted into the list.
|
|
||||||
if pc.activeSeparator == peer.listElement {
|
|
||||||
pc.activeSeparator = pc.activeSeparator.Prev()
|
|
||||||
}
|
|
||||||
_ = pc.knownPeers.Remove(peer.listElement)
|
|
||||||
}
|
|
||||||
if pc.activeSeparator == nil {
|
|
||||||
// If there isn't active peer yet.
|
|
||||||
peer.listElement = pc.knownPeers.PushBack(peer)
|
|
||||||
pc.activeSeparator = peer.listElement
|
|
||||||
} else if e := pc.knownPeers.InsertAfter(peer, pc.activeSeparator); e != nil {
|
|
||||||
peer.listElement = e
|
|
||||||
} else {
|
|
||||||
log.Warn("Failed to insert the peer after the separator", "peer", peer)
|
|
||||||
}
|
}
|
||||||
|
peer.listElement = pc.activePeers.PushBack(peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetActiveSeparator resets the active separator, denoting
|
// resetActive pushes the active peers to the end of the inactive peer
|
||||||
// that active peers are not considered active anymore.
|
// list, denoting that active peers are not considered active anymore.
|
||||||
func (pc *PeerContainer) resetActiveSeparator() {
|
func (pc *peerContainer) resetActive() {
|
||||||
pc.activeSeparator = nil
|
for pc.activePeers.Front() != nil {
|
||||||
|
pc.inactivePeers.PushBack(pc.activePeers.Remove(pc.activePeers.Front()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerBundle contains the peers belonging to a given IP address.
|
// peerBundle contains the peers belonging to a given IP address.
|
||||||
type PeerBundle struct {
|
type peerBundle struct {
|
||||||
// Location contains the geographical location based on the bundle's IP address.
|
// Location contains the geographical location based on the bundle's IP address.
|
||||||
Location *GeoLocation `json:"location,omitempty"`
|
Location *geoLocation `json:"location,omitempty"`
|
||||||
|
|
||||||
// KnownPeers is the inner map of the metered peer
|
// KnownPeers is the inner map of the metered peer
|
||||||
// maintainer data structure using the node ID as key.
|
// maintainer data structure using the node ID as key.
|
||||||
KnownPeers map[string]*KnownPeer `json:"knownPeers,omitempty"`
|
KnownPeers map[string]*knownPeer `json:"knownPeers,omitempty"`
|
||||||
|
|
||||||
// UnknownPeers contains the failed connection attempts of the
|
// Attempts contains the failed connection attempts of the
|
||||||
// peers belonging to a given IP address in chronological order.
|
// peers belonging to a given IP address in chronological order.
|
||||||
UnknownPeers []*UnknownPeer `json:"unknownPeers,omitempty"`
|
Attempts []*peerAttempt `json:"attempts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeKnown removes the known peer belonging to the
|
// removeKnown removes the known peer belonging to the
|
||||||
// given IP address and node ID from the peer tree.
|
// given IP address and node ID from the peer tree.
|
||||||
func (pc *PeerContainer) removeKnown(ip, id string) (events []*PeerEvent) {
|
func (pc *peerContainer) removeKnown(ip, id string) (events []*peerEvent) {
|
||||||
if bundle, ok := pc.Bundles[ip]; ok {
|
if bundle, ok := pc.Bundles[ip]; ok {
|
||||||
if _, ok := bundle.KnownPeers[id]; ok {
|
if _, ok := bundle.KnownPeers[id]; ok {
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
Remove: RemoveKnown,
|
Remove: RemoveKnown,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
ID: id,
|
ID: id,
|
||||||
})
|
})
|
||||||
delete(bundle.KnownPeers, id)
|
delete(bundle.KnownPeers, id)
|
||||||
} else {
|
} else {
|
||||||
log.Warn("No peer to remove", ip, id)
|
log.Warn("No peer to remove", "ip", ip, "id", id)
|
||||||
}
|
}
|
||||||
if len(bundle.KnownPeers) < 1 && len(bundle.UnknownPeers) < 1 {
|
if len(bundle.KnownPeers) < 1 && len(bundle.Attempts) < 1 {
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
Remove: RemoveBundle,
|
Remove: RemoveBundle,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
})
|
})
|
||||||
delete(pc.Bundles, ip)
|
delete(pc.Bundles, ip)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Warn("No bundle to remove", ip)
|
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeUnknown removes the unknown peer belonging to the
|
// removeAttempt removes the peer attempt belonging to the
|
||||||
// given IP address and node ID from the peer tree.
|
// given IP address and node ID from the peer tree.
|
||||||
func (pc *PeerContainer) removeUnknown(ip string) (events []*PeerEvent) {
|
func (pc *peerContainer) removeAttempt(ip string) (events []*peerEvent) {
|
||||||
if bundle, ok := pc.Bundles[ip]; ok {
|
if bundle, ok := pc.Bundles[ip]; ok {
|
||||||
if len(bundle.UnknownPeers) > 0 {
|
if len(bundle.Attempts) > 0 {
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
Remove: RemoveUnknown,
|
Remove: RemoveAttempt,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
})
|
})
|
||||||
bundle.UnknownPeers = bundle.UnknownPeers[1:]
|
bundle.Attempts = bundle.Attempts[1:]
|
||||||
}
|
}
|
||||||
if len(bundle.UnknownPeers) < 1 && len(bundle.KnownPeers) < 1 {
|
if len(bundle.Attempts) < 1 && len(bundle.KnownPeers) < 1 {
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
Remove: RemoveBundle,
|
Remove: RemoveBundle,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
})
|
})
|
||||||
delete(pc.Bundles, ip)
|
delete(pc.Bundles, ip)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Warn("No bundle to remove", ip)
|
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrInitKnownPeer inserts a new peer into the map, if the peer belonging
|
// knownPeer inserts a new peer into the map, if the peer belonging
|
||||||
// to the given IP address and node ID wasn't metered so far. Returns the peer
|
// to the given IP address and node ID wasn't metered so far. Returns the peer
|
||||||
// belonging to the given IP and ID as well as the events occurring during the
|
// belonging to the given IP and ID as well as the events occurring during the
|
||||||
// initialization.
|
// initialization.
|
||||||
func (bundle *PeerBundle) getOrInitKnownPeer(ip, id string, refresh time.Duration) (*KnownPeer, []*PeerEvent) {
|
func (bundle *peerBundle) knownPeer(ip, id string) (*knownPeer, []*peerEvent) {
|
||||||
var events []*PeerEvent
|
var events []*peerEvent
|
||||||
if _, ok := bundle.KnownPeers[id]; !ok {
|
if _, ok := bundle.KnownPeers[id]; !ok {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
ingress := emptyChartEntries(now, sampleLimit, refresh)
|
ingress := emptyChartEntries(now, sampleLimit)
|
||||||
egress := emptyChartEntries(now, sampleLimit, refresh)
|
egress := emptyChartEntries(now, sampleLimit)
|
||||||
events = append(events, &PeerEvent{
|
events = append(events, &peerEvent{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
ID: id,
|
ID: id,
|
||||||
Ingress: append([]*ChartEntry{}, ingress...),
|
Ingress: append([]*ChartEntry{}, ingress...),
|
||||||
Egress: append([]*ChartEntry{}, egress...),
|
Egress: append([]*ChartEntry{}, egress...),
|
||||||
})
|
})
|
||||||
bundle.KnownPeers[id] = &KnownPeer{
|
bundle.KnownPeers[id] = &knownPeer{
|
||||||
ip: ip,
|
ip: ip,
|
||||||
id: id,
|
id: id,
|
||||||
Ingress: ingress,
|
Ingress: ingress,
|
||||||
|
|
@ -337,8 +303,8 @@ func (bundle *PeerBundle) getOrInitKnownPeer(ip, id string, refresh time.Duratio
|
||||||
return bundle.KnownPeers[id], events
|
return bundle.KnownPeers[id], events
|
||||||
}
|
}
|
||||||
|
|
||||||
// KnownPeer contains the metered data of a particular peer.
|
// knownPeer contains the metered data of a particular peer.
|
||||||
type KnownPeer struct {
|
type knownPeer struct {
|
||||||
// Connected contains the timestamps of the peer's connection events.
|
// Connected contains the timestamps of the peer's connection events.
|
||||||
Connected []*time.Time `json:"connected,omitempty"`
|
Connected []*time.Time `json:"connected,omitempty"`
|
||||||
|
|
||||||
|
|
@ -361,8 +327,8 @@ type KnownPeer struct {
|
||||||
prevEgress float64
|
prevEgress float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnknownPeer contains a failed peer connection attempt's attributes.
|
// peerAttempt contains a failed peer connection attempt's attributes.
|
||||||
type UnknownPeer struct {
|
type peerAttempt struct {
|
||||||
// Connected contains the timestamp of the connection attempt's moment.
|
// Connected contains the timestamp of the connection attempt's moment.
|
||||||
Connected time.Time `json:"connected"`
|
Connected time.Time `json:"connected"`
|
||||||
|
|
||||||
|
|
@ -375,25 +341,27 @@ type RemovedPeerType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RemoveKnown RemovedPeerType = "known"
|
RemoveKnown RemovedPeerType = "known"
|
||||||
RemoveUnknown RemovedPeerType = "unknown"
|
RemoveAttempt RemovedPeerType = "attempt"
|
||||||
RemoveBundle RemovedPeerType = "bundle"
|
RemoveBundle RemovedPeerType = "bundle"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeerEvent contains the attributes of a peer event.
|
// peerEvent contains the attributes of a peer event.
|
||||||
type PeerEvent struct {
|
type peerEvent struct {
|
||||||
IP string `json:"ip,omitempty"` // IP address of the peer.
|
IP string `json:"ip,omitempty"` // IP address of the peer.
|
||||||
ID string `json:"id,omitempty"` // Node ID of the peer.
|
ID string `json:"id,omitempty"` // Node ID of the peer.
|
||||||
Remove RemovedPeerType `json:"remove,omitempty"` // Type of the peer that is to be removed.
|
Remove RemovedPeerType `json:"remove,omitempty"` // Type of the peer that is to be removed.
|
||||||
Location *GeoLocation `json:"location,omitempty"` // Geographical location of the peer.
|
Location *geoLocation `json:"location,omitempty"` // Geographical location of the peer.
|
||||||
Connected *time.Time `json:"connected,omitempty"` // Timestamp of the connection moment.
|
Connected *time.Time `json:"connected,omitempty"` // Timestamp of the connection moment.
|
||||||
Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
|
Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
|
||||||
Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
|
Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
|
||||||
Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
|
Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
|
||||||
}
|
}
|
||||||
|
|
||||||
// trafficMap
|
// trafficMap is a container for the periodically collected peer traffic.
|
||||||
type trafficMap map[string]map[string]float64
|
type trafficMap map[string]map[string]float64
|
||||||
|
|
||||||
|
// insert inserts a new value to the traffic map. Overwrites
|
||||||
|
// the value at the given ip and id if that already exists.
|
||||||
func (m *trafficMap) insert(ip, id string, val float64) {
|
func (m *trafficMap) insert(ip, id string, val float64) {
|
||||||
if _, ok := (*m)[ip]; !ok {
|
if _, ok := (*m)[ip]; !ok {
|
||||||
(*m)[ip] = make(map[string]float64)
|
(*m)[ip] = make(map[string]float64)
|
||||||
|
|
@ -407,12 +375,12 @@ func (db *Dashboard) collectPeerData() {
|
||||||
|
|
||||||
// Open the geodb database for IP to geographical information conversions.
|
// Open the geodb database for IP to geographical information conversions.
|
||||||
var err error
|
var err error
|
||||||
db.geodb, err = OpenGeoDB()
|
db.geodb, err = openGeoDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to open geodb", "err", err)
|
log.Warn("Failed to open geodb", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer db.geodb.Close()
|
defer db.geodb.close()
|
||||||
|
|
||||||
peerCh := make(chan p2p.MeteredPeerEvent, eventBufferLimit) // Peer event channel.
|
peerCh := make(chan p2p.MeteredPeerEvent, eventBufferLimit) // Peer event channel.
|
||||||
subPeer := p2p.SubscribeMeteredPeerEvent(peerCh) // Subscribe to peer events.
|
subPeer := p2p.SubscribeMeteredPeerEvent(peerCh) // Subscribe to peer events.
|
||||||
|
|
@ -449,7 +417,7 @@ func (db *Dashboard) collectPeerData() {
|
||||||
collectIngress := trafficCollector(p2p.MetricsInboundTraffic + "/")
|
collectIngress := trafficCollector(p2p.MetricsInboundTraffic + "/")
|
||||||
collectEgress := trafficCollector(p2p.MetricsOutboundTraffic + "/")
|
collectEgress := trafficCollector(p2p.MetricsOutboundTraffic + "/")
|
||||||
|
|
||||||
peers := NewPeerContainer(db.geodb, db.config.Refresh)
|
peers := newPeerContainer(db.geodb)
|
||||||
db.peerLock.Lock()
|
db.peerLock.Lock()
|
||||||
db.history.Network = &NetworkMessage{
|
db.history.Network = &NetworkMessage{
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
|
|
@ -458,7 +426,7 @@ func (db *Dashboard) collectPeerData() {
|
||||||
|
|
||||||
// newPeerEvents contains peer events, which trigger operations that
|
// newPeerEvents contains peer events, which trigger operations that
|
||||||
// will be executed on the peer tree after a metering period.
|
// will be executed on the peer tree after a metering period.
|
||||||
newPeerEvents := make([]*PeerEvent, 0, eventLimit)
|
newPeerEvents := make([]*peerEvent, 0, eventLimit)
|
||||||
ingress, egress := new(trafficMap), new(trafficMap)
|
ingress, egress := new(trafficMap), new(trafficMap)
|
||||||
*ingress, *egress = make(trafficMap), make(trafficMap)
|
*ingress, *egress = make(trafficMap), make(trafficMap)
|
||||||
|
|
||||||
|
|
@ -469,14 +437,14 @@ func (db *Dashboard) collectPeerData() {
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case p2p.PeerConnected:
|
case p2p.PeerConnected:
|
||||||
connected := now.Add(-event.Elapsed)
|
connected := now.Add(-event.Elapsed)
|
||||||
newPeerEvents = append(newPeerEvents, &PeerEvent{
|
newPeerEvents = append(newPeerEvents, &peerEvent{
|
||||||
IP: event.IP.String(),
|
IP: event.IP.String(),
|
||||||
ID: event.ID.String(),
|
ID: event.ID.String(),
|
||||||
Connected: &connected,
|
Connected: &connected,
|
||||||
})
|
})
|
||||||
case p2p.PeerDisconnected:
|
case p2p.PeerDisconnected:
|
||||||
ip, id := event.IP.String(), event.ID.String()
|
ip, id := event.IP.String(), event.ID.String()
|
||||||
newPeerEvents = append(newPeerEvents, &PeerEvent{
|
newPeerEvents = append(newPeerEvents, &peerEvent{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
ID: id,
|
ID: id,
|
||||||
Disconnected: &now,
|
Disconnected: &now,
|
||||||
|
|
@ -491,7 +459,7 @@ func (db *Dashboard) collectPeerData() {
|
||||||
egress.insert(ip, id, float64(event.Egress))
|
egress.insert(ip, id, float64(event.Egress))
|
||||||
case p2p.PeerHandshakeFailed:
|
case p2p.PeerHandshakeFailed:
|
||||||
connected := now.Add(-event.Elapsed)
|
connected := now.Add(-event.Elapsed)
|
||||||
newPeerEvents = append(newPeerEvents, &PeerEvent{
|
newPeerEvents = append(newPeerEvents, &peerEvent{
|
||||||
IP: event.IP.String(),
|
IP: event.IP.String(),
|
||||||
Connected: &connected,
|
Connected: &connected,
|
||||||
Disconnected: &now,
|
Disconnected: &now,
|
||||||
|
|
@ -517,9 +485,9 @@ func (db *Dashboard) collectPeerData() {
|
||||||
// the diff, otherwise the active peers can be removed.
|
// the diff, otherwise the active peers can be removed.
|
||||||
//
|
//
|
||||||
// After a metering period the active peers can become inactive,
|
// After a metering period the active peers can become inactive,
|
||||||
// so resetting the separator makes it sure, that only the active
|
// so at the beginning it is necessary to transpose them to the
|
||||||
// peers move to the protected part of the list.
|
// inactive list.
|
||||||
peers.resetActiveSeparator()
|
peers.resetActive()
|
||||||
for ip, bundle := range *ingress {
|
for ip, bundle := range *ingress {
|
||||||
for id := range bundle {
|
for id := range bundle {
|
||||||
// Only set the peers that are inserted both
|
// Only set the peers that are inserted both
|
||||||
|
|
@ -531,7 +499,7 @@ func (db *Dashboard) collectPeerData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var diff []*PeerEvent
|
var diff []*peerEvent
|
||||||
for i := 0; i < len(newPeerEvents); i++ {
|
for i := 0; i < len(newPeerEvents); i++ {
|
||||||
if newPeerEvents[i].IP == "" {
|
if newPeerEvents[i].IP == "" {
|
||||||
log.Warn("Peer event without IP", "event", *newPeerEvents[i])
|
log.Warn("Peer event without IP", "event", *newPeerEvents[i])
|
||||||
|
|
@ -541,18 +509,16 @@ func (db *Dashboard) collectPeerData() {
|
||||||
// There are two main branches of peer events coming from the event
|
// There are two main branches of peer events coming from the event
|
||||||
// feed, one belongs to the known peers, one to the unknown peers.
|
// feed, one belongs to the known peers, one to the unknown peers.
|
||||||
// If the event has node ID, it belongs to a known peer, otherwise
|
// If the event has node ID, it belongs to a known peer, otherwise
|
||||||
// to an unknown one.
|
// to an unknown one, which is considered as connection attempt.
|
||||||
//
|
//
|
||||||
// The extension can produce additional peer events, such
|
// The extension can produce additional peer events, such
|
||||||
// as remove, location and initial samples events.
|
// as remove, location and initial samples events.
|
||||||
if newPeerEvents[i].ID == "" {
|
if newPeerEvents[i].ID == "" {
|
||||||
diff = append(diff, peers.extendUnknown(newPeerEvents[i])...)
|
diff = append(diff, peers.handleAttempt(newPeerEvents[i])...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
diff = append(diff, peers.extendKnown(newPeerEvents[i])...)
|
diff = append(diff, peers.extendKnown(newPeerEvents[i])...)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
// Update the peer tree using the traffic maps.
|
// Update the peer tree using the traffic maps.
|
||||||
for ip, bundle := range peers.Bundles {
|
for ip, bundle := range peers.Bundles {
|
||||||
for id, peer := range bundle.KnownPeers {
|
for id, peer := range bundle.KnownPeers {
|
||||||
|
|
@ -568,11 +534,9 @@ func (db *Dashboard) collectPeerData() {
|
||||||
}
|
}
|
||||||
peer.prevIngress, peer.prevEgress = curIngress, curEgress
|
peer.prevIngress, peer.prevEgress = curIngress, curEgress
|
||||||
i := &ChartEntry{
|
i := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: deltaIngress,
|
Value: deltaIngress,
|
||||||
}
|
}
|
||||||
e := &ChartEntry{
|
e := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: deltaEgress,
|
Value: deltaEgress,
|
||||||
}
|
}
|
||||||
peer.Ingress = append(peer.Ingress, i)
|
peer.Ingress = append(peer.Ingress, i)
|
||||||
|
|
@ -584,7 +548,7 @@ func (db *Dashboard) collectPeerData() {
|
||||||
peer.Egress = peer.Egress[first:]
|
peer.Egress = peer.Egress[first:]
|
||||||
}
|
}
|
||||||
// Creating the traffic sample events.
|
// Creating the traffic sample events.
|
||||||
diff = append(diff, &PeerEvent{
|
diff = append(diff, &peerEvent{
|
||||||
IP: ip,
|
IP: ip,
|
||||||
ID: id,
|
ID: id,
|
||||||
Ingress: ChartEntries{i},
|
Ingress: ChartEntries{i},
|
||||||
|
|
@ -592,13 +556,8 @@ func (db *Dashboard) collectPeerData() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//ss, _ := json.MarshalIndent(db.history.Network, "", " ")
|
|
||||||
//fmt.Println(string(ss))
|
|
||||||
|
|
||||||
db.peerLock.Unlock()
|
db.peerLock.Unlock()
|
||||||
|
|
||||||
//s, _ := json.MarshalIndent(diff, "", " ")
|
|
||||||
//fmt.Println(string(s))
|
|
||||||
db.sendToAll(&Message{Network: &NetworkMessage{
|
db.sendToAll(&Message{Network: &NetworkMessage{
|
||||||
Diff: diff,
|
Diff: diff,
|
||||||
}})
|
}})
|
||||||
|
|
|
||||||
|
|
@ -92,39 +92,29 @@ func (db *Dashboard) collectSystemData() {
|
||||||
prevDiskRead = curDiskRead
|
prevDiskRead = curDiskRead
|
||||||
prevDiskWrite = curDiskWrite
|
prevDiskWrite = curDiskWrite
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
runtime.ReadMemStats(&mem)
|
runtime.ReadMemStats(&mem)
|
||||||
activeMemory := &ChartEntry{
|
activeMemory := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: float64(mem.Alloc) / frequency,
|
Value: float64(mem.Alloc) / frequency,
|
||||||
}
|
}
|
||||||
virtualMemory := &ChartEntry{
|
virtualMemory := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: float64(mem.Sys) / frequency,
|
Value: float64(mem.Sys) / frequency,
|
||||||
}
|
}
|
||||||
networkIngress := &ChartEntry{
|
networkIngress := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: deltaNetworkIngress / frequency,
|
Value: deltaNetworkIngress / frequency,
|
||||||
}
|
}
|
||||||
networkEgress := &ChartEntry{
|
networkEgress := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: deltaNetworkEgress / frequency,
|
Value: deltaNetworkEgress / frequency,
|
||||||
}
|
}
|
||||||
processCPU := &ChartEntry{
|
processCPU := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: deltaProcessCPUTime / frequency / numCPU * 100,
|
Value: deltaProcessCPUTime / frequency / numCPU * 100,
|
||||||
}
|
}
|
||||||
systemCPU := &ChartEntry{
|
systemCPU := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: float64(deltaSystemCPUUsage.Sys+deltaSystemCPUUsage.User) / frequency / numCPU,
|
Value: float64(deltaSystemCPUUsage.Sys+deltaSystemCPUUsage.User) / frequency / numCPU,
|
||||||
}
|
}
|
||||||
diskRead := &ChartEntry{
|
diskRead := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: float64(deltaDiskRead) / frequency,
|
Value: float64(deltaDiskRead) / frequency,
|
||||||
}
|
}
|
||||||
diskWrite := &ChartEntry{
|
diskWrite := &ChartEntry{
|
||||||
Time: now,
|
|
||||||
Value: float64(deltaDiskWrite) / frequency,
|
Value: float64(deltaDiskWrite) / frequency,
|
||||||
}
|
}
|
||||||
db.sysLock.Lock()
|
db.sysLock.Lock()
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ func (c *meteredConn) Write(b []byte) (n int, err error) {
|
||||||
// the ingress and the egress traffic registries using the peer's IP and node ID,
|
// the ingress and the egress traffic registries using the peer's IP and node ID,
|
||||||
// also emits connect event.
|
// also emits connect event.
|
||||||
func (c *meteredConn) handshakeDone(id enode.ID) {
|
func (c *meteredConn) handshakeDone(id enode.ID) {
|
||||||
|
// TODO (kurkomisi): use the node URL instead of the pure node ID. (the String() method of *Node)
|
||||||
if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
|
if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
|
||||||
// Don't register the peer in the traffic registries.
|
// Don't register the peer in the traffic registries.
|
||||||
atomic.AddInt32(&meteredPeerCount, -1)
|
atomic.AddInt32(&meteredPeerCount, -1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue