github.com/m3shine/gochain@v2.2.26+incompatible/netstats/netstats.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package netstats implements the network stats reporting service. 18 package netstats 19 20 import ( 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/big" 26 "net" 27 "regexp" 28 "runtime" 29 "strconv" 30 "strings" 31 "time" 32 33 "go.opencensus.io/trace" 34 35 "github.com/gochain-io/gochain/common" 36 "github.com/gochain-io/gochain/common/mclock" 37 "github.com/gochain-io/gochain/consensus" 38 "github.com/gochain-io/gochain/core" 39 "github.com/gochain-io/gochain/core/types" 40 "github.com/gochain-io/gochain/eth" 41 "github.com/gochain-io/gochain/les" 42 "github.com/gochain-io/gochain/log" 43 "github.com/gochain-io/gochain/p2p" 44 "github.com/gochain-io/gochain/rpc" 45 "golang.org/x/net/websocket" 46 ) 47 48 const ( 49 // historyUpdateRange is the number of blocks a node should report upon login or 50 // history request. 51 historyUpdateRange = 50 52 ) 53 54 type txPool interface { 55 SubscribeNewTxsEvent(chan<- core.NewTxsEvent, string) 56 UnsubscribeNewTxsEvent(chan<- core.NewTxsEvent) 57 } 58 59 type blockChain interface { 60 SubscribeChainHeadEvent(chan<- core.ChainHeadEvent, string) 61 UnsubscribeChainHeadEvent(chan<- core.ChainHeadEvent) 62 } 63 64 // Service implements an GoChain netstats reporting daemon that pushes local 65 // chain statistics up to a monitoring server. 66 type Service struct { 67 server *p2p.Server // Peer-to-peer server to retrieve networking infos 68 eth *eth.GoChain // Full GoChain service if monitoring a full node 69 les *les.LightGoChain // Light GoChain service if monitoring a light node 70 engine consensus.Engine // Consensus engine to retrieve variadic block fields 71 72 node string // Name of the node to display on the monitoring page 73 pass string // Password to authorize access to the monitoring page 74 host string // Remote address of the monitoring service 75 76 pongCh chan struct{} // Pong notifications are fed into this channel 77 histCh chan []uint64 // History request block numbers are fed into this channel 78 } 79 80 type Config struct { 81 Name string `toml:",omitempty"` 82 Secret string `toml:",omitempty"` 83 URL string `toml:",omitempty"` 84 } 85 86 // ParseConfig parses the netstats flag. It has the form "nodename:secret@host:port". 87 func ParseConfig(flag string) (Config, error) { 88 re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") 89 parts := re.FindStringSubmatch(flag) 90 if len(parts) != 5 { 91 return Config{}, fmt.Errorf("invalid netstats flag: \"%s\", should be nodename:secret@host:port", flag) 92 } 93 return Config{ 94 Name: parts[1], 95 Secret: parts[3], 96 URL: parts[4], 97 }, nil 98 } 99 100 // New returns a monitoring service ready for stats reporting. 101 func New(cfg Config, ethServ *eth.GoChain, lesServ *les.LightGoChain) *Service { 102 // Assemble and return the stats service 103 var engine consensus.Engine 104 if ethServ != nil { 105 engine = ethServ.Engine() 106 } else { 107 engine = lesServ.Engine() 108 } 109 return &Service{ 110 eth: ethServ, 111 les: lesServ, 112 engine: engine, 113 node: cfg.Name, 114 pass: cfg.Secret, 115 host: cfg.URL, 116 pongCh: make(chan struct{}), 117 histCh: make(chan []uint64, 1), 118 } 119 } 120 121 // Protocols implements node.Service, returning the P2P network protocols used 122 // by the stats service (nil as it doesn't use the devp2p overlay network). 123 func (s *Service) Protocols() []p2p.Protocol { return nil } 124 125 // APIs implements node.Service, returning the RPC API endpoints provided by the 126 // stats service (nil as it doesn't provide any user callable APIs). 127 func (s *Service) APIs() []rpc.API { return nil } 128 129 // Start implements node.Service, starting up the monitoring and reporting daemon. 130 func (s *Service) Start(server *p2p.Server) error { 131 s.server = server 132 go s.loop() 133 134 log.Info("Stats daemon started") 135 return nil 136 } 137 138 // Stop implements node.Service, terminating the monitoring and reporting daemon. 139 func (s *Service) Stop() error { 140 log.Info("Stats daemon stopped") 141 return nil 142 } 143 144 // loop keeps trying to connect to the netstats server, reporting chain events 145 // until termination. 146 func (s *Service) loop() { 147 // Subscribe to chain events to execute updates on 148 var blockchain blockChain 149 var txpool txPool 150 if s.eth != nil { 151 blockchain = s.eth.BlockChain() 152 txpool = s.eth.TxPool() 153 } else { 154 blockchain = s.les.BlockChain() 155 txpool = s.les.TxPool() 156 } 157 158 // Start a goroutine that exhausts the subscriptions to avoid events piling up 159 var ( 160 chainHeadCh = make(chan core.ChainHeadEvent, 1) 161 txEventCh = make(chan core.NewTxsEvent, 1) 162 quitCh = make(chan struct{}) 163 headCh = make(chan *types.Block, 1) 164 txCh = make(chan struct{}, 1) 165 ) 166 blockchain.SubscribeChainHeadEvent(chainHeadCh, "netstats.Service") 167 txpool.SubscribeNewTxsEvent(txEventCh, "netstats.Service") 168 go func() { 169 defer close(quitCh) 170 defer blockchain.UnsubscribeChainHeadEvent(chainHeadCh) 171 defer txpool.UnsubscribeNewTxsEvent(txEventCh) 172 173 var lastTx mclock.AbsTime 174 for { 175 select { 176 // Notify of chain head events. 177 case head, ok := <-chainHeadCh: 178 if !ok { 179 return 180 } 181 select { 182 case headCh <- head.Block: 183 default: 184 } 185 186 // Notify of new transaction events, but drop if too frequent 187 case _, ok := <-txEventCh: 188 if !ok { 189 return 190 } 191 if time.Duration(mclock.Now()-lastTx) < time.Second { 192 continue 193 } 194 lastTx = mclock.Now() 195 196 select { 197 case txCh <- struct{}{}: 198 default: 199 } 200 } 201 } 202 }() 203 // Resolve the URL, defaulting to TLS, but falling back to none too 204 path := fmt.Sprintf("%s/api", s.host) 205 urls := []string{path} 206 207 if !strings.Contains(path, "://") { // url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779) 208 urls = []string{"wss://" + path, "ws://" + path} 209 } 210 // Loop reporting until termination 211 for { 212 conn := s.connect(urls) 213 if conn == nil { 214 select { 215 case <-quitCh: 216 return 217 case <-time.After(10 * time.Second): 218 } 219 continue 220 } 221 // Respond to specific requests. 222 go s.readLoop(conn) 223 // Scheduled and triggered reports. 224 s.reportLoop(conn, quitCh, txCh, headCh) 225 select { 226 case <-quitCh: 227 return 228 default: 229 } 230 } 231 } 232 233 // connect establishes a websocket connection to the server on any supported URL, or returns nil if unable. 234 func (s *Service) connect(urls []string) *websocket.Conn { 235 var ( 236 conf *websocket.Config 237 conn *websocket.Conn 238 err error 239 ) 240 for _, url := range urls { 241 if conf, err = websocket.NewConfig(url, "http://localhost/"); err != nil { 242 continue 243 } 244 conf.Dialer = &net.Dialer{Timeout: 5 * time.Second} 245 if conn, err = websocket.DialConfig(conf); err == nil { 246 break 247 } 248 } 249 if err != nil { 250 log.Warn("Stats server unreachable", "err", err) 251 return nil 252 } 253 // Authenticate the client with the server 254 if err = s.login(conn); err != nil { 255 log.Warn("Stats login failed", "err", err) 256 conn.Close() 257 return nil 258 } 259 return conn 260 } 261 262 // readLoop loops as long as the connection is alive and retrieves data packets 263 // from the network socket. If any of them match an active request, it forwards 264 // it, if they themselves are requests it initiates a reply, and lastly it drops 265 // unknown packets. 266 func (s *Service) readLoop(conn *websocket.Conn) { 267 // If the read loop exists, close the connection 268 defer conn.Close() 269 270 for { 271 // Retrieve the next generic network packet and bail out on error 272 var msg map[string][]interface{} 273 if err := websocket.JSON.Receive(conn, &msg); err != nil { 274 log.Warn("Failed to decode stats server message", "err", err) 275 return 276 } 277 log.Trace("Received message from stats server", "msg", msg) 278 if len(msg["emit"]) == 0 { 279 log.Warn("Stats server sent non-broadcast", "msg", msg) 280 return 281 } 282 command, ok := msg["emit"][0].(string) 283 if !ok { 284 log.Warn("Invalid stats server message type", "type", msg["emit"][0]) 285 return 286 } 287 // If the message is a ping reply, deliver (someone must be listening!) 288 if len(msg["emit"]) == 2 && command == "node-pong" { 289 select { 290 case s.pongCh <- struct{}{}: 291 // Pong delivered, continue listening 292 continue 293 default: 294 // Ping routine dead, abort 295 log.Warn("Stats server pinger seems to have died") 296 return 297 } 298 } 299 // If the message is a history request, forward to the event processor 300 if len(msg["emit"]) == 2 && command == "history" { 301 // Make sure the request is valid and doesn't crash us 302 request, ok := msg["emit"][1].(map[string]interface{}) 303 if !ok { 304 log.Warn("Invalid stats history request", "msg", msg["emit"][1]) 305 s.histCh <- nil 306 continue // Netstats sometime sends invalid history requests, ignore those 307 } 308 list, ok := request["list"].([]interface{}) 309 if !ok { 310 log.Warn("Invalid stats history block list", "list", request["list"]) 311 return 312 } 313 // Convert the block number list to an integer list 314 numbers := make([]uint64, len(list)) 315 for i, num := range list { 316 n, ok := num.(float64) 317 if !ok { 318 log.Warn("Invalid stats history block number", "number", num) 319 return 320 } 321 numbers[i] = uint64(n) 322 } 323 select { 324 case s.histCh <- numbers: 325 continue 326 default: 327 } 328 } 329 // Report anything else and continue 330 log.Info("Unknown stats message", "msg", msg) 331 } 332 } 333 334 // reportLoop sends status updates until the connection breaks. 335 func (s *Service) reportLoop(conn *websocket.Conn, quitCh, txCh <-chan struct{}, headCh <-chan *types.Block) { 336 defer conn.Close() 337 338 // Send the initial stats so our node looks decent from the get go 339 if err := s.report(context.Background(), conn); err != nil { 340 log.Warn("Initial stats report failed", "err", err) 341 return 342 } 343 344 fullReport := time.NewTicker(5 * time.Second) 345 defer fullReport.Stop() 346 347 var err error 348 for err == nil { 349 select { 350 case <-quitCh: 351 return 352 353 case <-fullReport.C: 354 ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-fullReport") 355 if err = s.report(ctx, conn); err != nil { 356 log.Warn("Full stats report failed", "err", err) 357 } 358 span.End() 359 case list := <-s.histCh: 360 ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-histCh") 361 if err = s.reportHistory(ctx, conn, list); err != nil { 362 log.Warn("Requested history report failed", "err", err) 363 } 364 span.End() 365 case head := <-headCh: 366 ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-headCh") 367 if err = s.reportBlock(ctx, conn, head); err != nil { 368 log.Warn("Block stats report failed", "err", err) 369 } 370 if err = s.reportPending(ctx, conn); err != nil { 371 log.Warn("Post-block transaction stats report failed", "err", err) 372 } 373 span.End() 374 case <-txCh: 375 ctx, span := trace.StartSpan(context.Background(), "Service.reportLoop-txCh") 376 if err = s.reportPending(ctx, conn); err != nil { 377 log.Warn("Transaction stats report failed", "err", err) 378 } 379 span.End() 380 } 381 } 382 } 383 384 // nodeInfo is the collection of metainformation about a node that is displayed 385 // on the monitoring page. 386 type nodeInfo struct { 387 Name string `json:"name"` 388 Node string `json:"node"` 389 Port int `json:"port"` 390 Network string `json:"net"` 391 Protocol string `json:"protocol"` 392 API string `json:"api"` 393 Os string `json:"os"` 394 OsVer string `json:"os_v"` 395 Client string `json:"client"` 396 History bool `json:"canUpdateHistory"` 397 } 398 399 // authMsg is the authentication infos needed to login to a monitoring server. 400 type authMsg struct { 401 Id string `json:"id"` 402 Info nodeInfo `json:"info"` 403 Secret string `json:"secret"` 404 } 405 406 // login tries to authorize the client at the remote server. 407 func (s *Service) login(conn *websocket.Conn) error { 408 // Construct and send the login authentication 409 infos := s.server.NodeInfo() 410 411 var network, protocol string 412 if info := infos.Protocols["eth"]; info != nil { 413 network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) 414 protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) 415 } else { 416 network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) 417 protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) 418 } 419 auth := &authMsg{ 420 Id: s.node, 421 Info: nodeInfo{ 422 Name: s.node, 423 Node: infos.Name, 424 Port: infos.Ports.Listener, 425 Network: network, 426 Protocol: protocol, 427 API: "No", 428 Os: runtime.GOOS, 429 OsVer: runtime.GOARCH, 430 Client: "0.1.1", 431 History: true, 432 }, 433 Secret: s.pass, 434 } 435 login := map[string][]interface{}{ 436 "emit": {"hello", auth}, 437 } 438 if err := websocket.JSON.Send(conn, login); err != nil { 439 return err 440 } 441 // Retrieve the remote ack or connection termination 442 var ack map[string][]string 443 if err := websocket.JSON.Receive(conn, &ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { 444 return errors.New("unauthorized") 445 } 446 return nil 447 } 448 449 // report collects all possible data to report and send it to the stats server. 450 // This should only be used on reconnects or rarely to avoid overloading the 451 // server. Use the individual methods for reporting subscribed events. 452 func (s *Service) report(ctx context.Context, conn *websocket.Conn) error { 453 ctx, span := trace.StartSpan(ctx, "Service.report") 454 defer span.End() 455 456 if err := s.reportLatency(ctx, conn); err != nil { 457 return err 458 } 459 if err := s.reportBlock(ctx, conn, nil); err != nil { 460 return err 461 } 462 if err := s.reportPending(ctx, conn); err != nil { 463 return err 464 } 465 if err := s.reportStats(ctx, conn); err != nil { 466 return err 467 } 468 return nil 469 } 470 471 // reportLatency sends a ping request to the server, measures the RTT time and 472 // finally sends a latency update. 473 func (s *Service) reportLatency(ctx context.Context, conn *websocket.Conn) error { 474 ctx, span := trace.StartSpan(ctx, "Service.reportLatency") 475 defer span.End() 476 477 // Send the current time to the netstats server 478 start := time.Now() 479 480 ping := map[string][]interface{}{ 481 "emit": {"node-ping", map[string]string{ 482 "id": s.node, 483 "clientTime": start.String(), 484 }}, 485 } 486 if err := websocket.JSON.Send(conn, ping); err != nil { 487 return err 488 } 489 // Wait for the pong request to arrive back 490 select { 491 case <-s.pongCh: 492 // Pong delivered, report the latency 493 case <-time.After(5 * time.Second): 494 // Ping timeout, abort 495 return errors.New("ping timed out") 496 } 497 latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000)) 498 499 // Send back the measured latency 500 log.Trace("Sending measured latency to netstats", "latency", latency) 501 502 stats := map[string][]interface{}{ 503 "emit": {"latency", map[string]string{ 504 "id": s.node, 505 "latency": latency, 506 }}, 507 } 508 return websocket.JSON.Send(conn, stats) 509 } 510 511 // blockStats is the information to report about individual blocks. 512 type blockStats struct { 513 Number *big.Int `json:"number"` 514 Hash common.Hash `json:"hash"` 515 ParentHash common.Hash `json:"parentHash"` 516 Timestamp *big.Int `json:"timestamp"` 517 Miner common.Address `json:"miner"` 518 GasUsed uint64 `json:"gasUsed"` 519 GasLimit uint64 `json:"gasLimit"` 520 Diff string `json:"difficulty"` 521 TotalDiff string `json:"totalDifficulty"` 522 Txs []txStats `json:"transactions"` 523 TxCount uint64 `json:"transactionCount"` 524 TxHash common.Hash `json:"transactionsRoot"` 525 Root common.Hash `json:"stateRoot"` 526 Uncles uncleStats `json:"uncles"` 527 } 528 529 // txStats is the information to report about individual transactions. 530 type txStats struct { 531 Hash common.Hash `json:"hash"` 532 } 533 534 // uncleStats is a custom wrapper around an uncle array to force serializing 535 // empty arrays instead of returning null for them. 536 type uncleStats []*types.Header 537 538 func (s uncleStats) MarshalJSON() ([]byte, error) { 539 if uncles := ([]*types.Header)(s); len(uncles) > 0 { 540 return json.Marshal(uncles) 541 } 542 return []byte("[]"), nil 543 } 544 545 // reportBlock retrieves the current chain head and repors it to the stats server. 546 func (s *Service) reportBlock(ctx context.Context, conn *websocket.Conn, block *types.Block) error { 547 ctx, span := trace.StartSpan(ctx, "Service.reportBlock") 548 defer span.End() 549 550 // Gather the block details from the header or block chain 551 details := s.assembleBlockStats(ctx, block) 552 553 // Assemble the block report and send it to the server 554 log.Trace("Sending new block to netstats", "number", details.Number, "hash", details.Hash) 555 556 stats := map[string]interface{}{ 557 "id": s.node, 558 "block": details, 559 } 560 report := map[string][]interface{}{ 561 "emit": {"block", stats}, 562 } 563 return websocket.JSON.Send(conn, report) 564 } 565 566 // assembleBlockStats retrieves any required metadata to report a single block 567 // and assembles the block stats. If block is nil, the current head is processed. 568 func (s *Service) assembleBlockStats(ctx context.Context, block *types.Block) *blockStats { 569 ctx, span := trace.StartSpan(ctx, "Service.assembleBlockStats") 570 defer span.End() 571 572 // Gather the block infos from the local blockchain 573 var ( 574 header *types.Header 575 td *big.Int 576 txs []txStats 577 txCount int 578 uncles []*types.Header 579 ) 580 if s.eth != nil { 581 // Full nodes have all needed information available 582 if block == nil { 583 block = s.eth.BlockChain().CurrentBlock() 584 } 585 header = block.Header() 586 td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) 587 588 txCount = len(block.Transactions()) 589 txs = make([]txStats, txCount) 590 for i, tx := range block.Transactions() { 591 txs[i].Hash = tx.Hash() 592 } 593 uncles = block.Uncles() 594 } else { 595 // Light nodes would need on-demand lookups for transactions/uncles, skip 596 if block != nil { 597 header = block.Header() 598 } else { 599 header = s.les.BlockChain().CurrentHeader() 600 } 601 td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) 602 txs = []txStats{} 603 } 604 // Assemble and return the block stats 605 author, _ := s.engine.Author(header) 606 607 return &blockStats{ 608 Number: header.Number, 609 Hash: header.Hash(), 610 ParentHash: header.ParentHash, 611 Timestamp: header.Time, 612 Miner: author, 613 GasUsed: header.GasUsed, 614 GasLimit: header.GasLimit, 615 Diff: header.Difficulty.String(), 616 TotalDiff: td.String(), 617 TxCount: uint64(txCount), 618 Txs: txs, 619 TxHash: header.TxHash, 620 Root: header.Root, 621 Uncles: uncles, 622 } 623 } 624 625 // reportHistory retrieves the most recent batch of blocks and reports it to the 626 // stats server. 627 func (s *Service) reportHistory(ctx context.Context, conn *websocket.Conn, list []uint64) error { 628 ctx, span := trace.StartSpan(ctx, "Service.reportHistory") 629 defer span.End() 630 631 // Figure out the indexes that need reporting 632 indexes := make([]uint64, 0, historyUpdateRange) 633 if len(list) > 0 { 634 // Specific indexes requested, send them back in particular 635 indexes = append(indexes, list...) 636 } else { 637 // No indexes requested, send back the top ones 638 var head int64 639 if s.eth != nil { 640 head = s.eth.BlockChain().CurrentHeader().Number.Int64() 641 } else { 642 head = s.les.BlockChain().CurrentHeader().Number.Int64() 643 } 644 start := head - historyUpdateRange + 1 645 if start < 0 { 646 start = 0 647 } 648 for i := uint64(start); i <= uint64(head); i++ { 649 indexes = append(indexes, i) 650 } 651 } 652 // Gather the batch of blocks to report 653 history := make([]*blockStats, len(indexes)) 654 for i, number := range indexes { 655 // Retrieve the next block if it's known to us 656 var block *types.Block 657 if s.eth != nil { 658 block = s.eth.BlockChain().GetBlockByNumber(number) 659 } else { 660 if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil { 661 block = types.NewBlockWithHeader(header) 662 } 663 } 664 // If we do have the block, add to the history and continue 665 if block != nil { 666 history[len(history)-1-i] = s.assembleBlockStats(ctx, block) 667 continue 668 } 669 // Ran out of blocks, cut the report short and send 670 history = history[len(history)-i:] 671 break 672 } 673 // Assemble the history report and send it to the server 674 if len(history) > 0 { 675 log.Trace("Sending historical blocks to netstats", "first", history[0].Number, "last", history[len(history)-1].Number) 676 } else { 677 log.Trace("No history to send to stats server") 678 } 679 stats := map[string]interface{}{ 680 "id": s.node, 681 "history": history, 682 } 683 report := map[string][]interface{}{ 684 "emit": {"history", stats}, 685 } 686 return websocket.JSON.Send(conn, report) 687 } 688 689 // pendStats is the information to report about pending transactions. 690 type pendStats struct { 691 Pending int `json:"pending"` 692 } 693 694 // reportPending retrieves the current number of pending transactions and reports 695 // it to the stats server. 696 func (s *Service) reportPending(ctx context.Context, conn *websocket.Conn) error { 697 ctx, span := trace.StartSpan(ctx, "Service.reportPending") 698 defer span.End() 699 700 // Retrieve the pending count from the local blockchain 701 var pending int 702 if s.eth != nil { 703 pending, _ = s.eth.TxPool().StatsCtx(ctx) 704 } else { 705 pending = s.les.TxPool().Stats() 706 } 707 // Assemble the transaction stats and send it to the server 708 log.Trace("Sending pending transactions to netstats", "count", pending) 709 710 stats := map[string]interface{}{ 711 "id": s.node, 712 "stats": &pendStats{ 713 Pending: pending, 714 }, 715 } 716 report := map[string][]interface{}{ 717 "emit": {"pending", stats}, 718 } 719 return websocket.JSON.Send(conn, report) 720 } 721 722 // nodeStats is the information to report about the local node. 723 type nodeStats struct { 724 Active bool `json:"active"` 725 Syncing bool `json:"syncing"` 726 Mining bool `json:"mining"` 727 Hashrate int `json:"hashrate"` 728 Peers int `json:"peers"` 729 GasPrice int `json:"gasPrice"` 730 Uptime int `json:"uptime"` 731 } 732 733 // reportPending retrieves various stats about the node at the networking and 734 // mining layer and reports it to the stats server. 735 func (s *Service) reportStats(ctx context.Context, conn *websocket.Conn) error { 736 ctx, span := trace.StartSpan(ctx, "Service.reportStats") 737 defer span.End() 738 739 // Gather the syncing and mining infos from the local miner instance 740 var ( 741 mining bool 742 syncing bool 743 gasprice int 744 ) 745 if s.eth != nil { 746 mining = s.eth.Miner().Mining() 747 748 sync := s.eth.Downloader().Progress() 749 syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock 750 751 price, _ := s.eth.ApiBackend.SuggestPrice(context.Background()) 752 gasprice = int(price.Uint64()) 753 } else { 754 sync := s.les.Downloader().Progress() 755 syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock 756 } 757 // Assemble the node stats and send it to the server 758 log.Trace("Sending node details to netstats") 759 760 stats := map[string]interface{}{ 761 "id": s.node, 762 "stats": &nodeStats{ 763 Active: true, 764 Mining: mining, 765 Peers: s.server.PeerCount(), 766 GasPrice: gasprice, 767 Syncing: syncing, 768 Uptime: 100, 769 }, 770 } 771 report := map[string][]interface{}{ 772 "emit": {"stats", stats}, 773 } 774 return websocket.JSON.Send(conn, report) 775 }