github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/dashboard/dashboard.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 19:16:37</date>
    10  //</624450087291981824>
    11  
    12  
    13  package dashboard
    14  
    15  //执行:生成纱线--cwd./资产安装
    16  //go:生成纱线——cwd./资产构建
    17  //go:生成go bindata-nometadata-o assets.go-prefix assets-nocompress-pkg dashboard assets/index.html assets/bundle.js
    18  //go:generate sh-c“sed's var bundlejs//nolint:misspell\\n&assets.go>assets.go.tmp&&mv assets.go.tmp assets.go”
    19  //go:generate sh-c“sed's var indexhtml//nolint:misspell\\n&assets.go>assets.go.tmp&&mv assets.go.tmp assets.go”
    20  //go:生成gofmt-w-s资产。
    21  
    22  import (
    23  	"fmt"
    24  	"net"
    25  	"net/http"
    26  	"runtime"
    27  	"sync"
    28  	"sync/atomic"
    29  	"time"
    30  
    31  	"io"
    32  
    33  	"github.com/elastic/gosigar"
    34  	"github.com/ethereum/go-ethereum/log"
    35  	"github.com/ethereum/go-ethereum/metrics"
    36  	"github.com/ethereum/go-ethereum/p2p"
    37  	"github.com/ethereum/go-ethereum/params"
    38  	"github.com/ethereum/go-ethereum/rpc"
    39  	"github.com/mohae/deepcopy"
    40  	"golang.org/x/net/websocket"
    41  )
    42  
    43  const (
    44  activeMemorySampleLimit   = 200 //活动内存数据样本的最大数目
    45  virtualMemorySampleLimit  = 200 //虚拟内存数据示例的最大数目
    46  networkIngressSampleLimit = 200 //最大网络入口数据样本数
    47  networkEgressSampleLimit  = 200 //网络出口数据样本的最大数目
    48  processCPUSampleLimit     = 200 //最大进程CPU数据样本数
    49  systemCPUSampleLimit      = 200 //系统CPU数据样本的最大数目
    50  diskReadSampleLimit       = 200 //磁盘读取数据样本的最大数目
    51  diskWriteSampleLimit      = 200 //磁盘写入数据示例的最大数目
    52  )
    53  
    54  var nextID uint32 //下一个连接ID
    55  
    56  //仪表板包含仪表板内部。
    57  type Dashboard struct {
    58  	config *Config
    59  
    60  	listener net.Listener
    61  conns    map[uint32]*client //当前活动的WebSocket连接
    62  	history  *Message
    63  lock     sync.RWMutex //锁定保护仪表板内部
    64  
    65  	logdir string
    66  
    67  quit chan chan error //用于优美出口的通道
    68  	wg   sync.WaitGroup
    69  }
    70  
    71  //客户端表示与远程浏览器的活动WebSocket连接。
    72  type client struct {
    73  conn   *websocket.Conn //特定的Live WebSocket连接
    74  msg    chan *Message   //更新消息的消息队列
    75  logger log.Logger      //Logger for the particular live websocket connection
    76  }
    77  
    78  //新建创建具有给定配置的新仪表板实例。
    79  func New(config *Config, commit string, logdir string) *Dashboard {
    80  	now := time.Now()
    81  	versionMeta := ""
    82  	if len(params.VersionMeta) > 0 {
    83  		versionMeta = fmt.Sprintf(" (%s)", params.VersionMeta)
    84  	}
    85  	return &Dashboard{
    86  		conns:  make(map[uint32]*client),
    87  		config: config,
    88  		quit:   make(chan chan error),
    89  		history: &Message{
    90  			General: &GeneralMessage{
    91  				Commit:  commit,
    92  				Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
    93  			},
    94  			System: &SystemMessage{
    95  				ActiveMemory:   emptyChartEntries(now, activeMemorySampleLimit, config.Refresh),
    96  				VirtualMemory:  emptyChartEntries(now, virtualMemorySampleLimit, config.Refresh),
    97  				NetworkIngress: emptyChartEntries(now, networkIngressSampleLimit, config.Refresh),
    98  				NetworkEgress:  emptyChartEntries(now, networkEgressSampleLimit, config.Refresh),
    99  				ProcessCPU:     emptyChartEntries(now, processCPUSampleLimit, config.Refresh),
   100  				SystemCPU:      emptyChartEntries(now, systemCPUSampleLimit, config.Refresh),
   101  				DiskRead:       emptyChartEntries(now, diskReadSampleLimit, config.Refresh),
   102  				DiskWrite:      emptyChartEntries(now, diskWriteSampleLimit, config.Refresh),
   103  			},
   104  		},
   105  		logdir: logdir,
   106  	}
   107  }
   108  
   109  //EmptyChartEntries返回一个包含空样本数量限制的ChartEntry数组。
   110  func emptyChartEntries(t time.Time, limit int, refresh time.Duration) ChartEntries {
   111  	ce := make(ChartEntries, limit)
   112  	for i := 0; i < limit; i++ {
   113  		ce[i] = &ChartEntry{
   114  			Time: t.Add(-time.Duration(i) * refresh),
   115  		}
   116  	}
   117  	return ce
   118  }
   119  
   120  //协议实现了node.service接口。
   121  func (db *Dashboard) Protocols() []p2p.Protocol { return nil }
   122  
   123  //API实现了node.service接口。
   124  func (db *Dashboard) APIs() []rpc.API { return nil }
   125  
   126  //Start启动数据收集线程和仪表板的侦听服务器。
   127  //实现node.service接口。
   128  func (db *Dashboard) Start(server *p2p.Server) error {
   129  	log.Info("Starting dashboard")
   130  
   131  	db.wg.Add(2)
   132  	go db.collectData()
   133  	go db.streamLogs()
   134  
   135  	http.HandleFunc("/", db.webHandler)
   136  	http.Handle("/api", websocket.Handler(db.apiHandler))
   137  
   138  	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", db.config.Host, db.config.Port))
   139  	if err != nil {
   140  		return err
   141  	}
   142  	db.listener = listener
   143  
   144  	go http.Serve(listener, nil)
   145  
   146  	return nil
   147  }
   148  
   149  //stop停止数据收集线程和仪表板的连接侦听器。
   150  //实现node.service接口。
   151  func (db *Dashboard) Stop() error {
   152  //关闭连接侦听器。
   153  	var errs []error
   154  	if err := db.listener.Close(); err != nil {
   155  		errs = append(errs, err)
   156  	}
   157  //关闭收集器。
   158  	errc := make(chan error, 1)
   159  	for i := 0; i < 2; i++ {
   160  		db.quit <- errc
   161  		if err := <-errc; err != nil {
   162  			errs = append(errs, err)
   163  		}
   164  	}
   165  //关闭连接。
   166  	db.lock.Lock()
   167  	for _, c := range db.conns {
   168  		if err := c.conn.Close(); err != nil {
   169  			c.logger.Warn("Failed to close connection", "err", err)
   170  		}
   171  	}
   172  	db.lock.Unlock()
   173  
   174  //等到每一条血路都结束。
   175  	db.wg.Wait()
   176  	log.Info("Dashboard stopped")
   177  
   178  	var err error
   179  	if len(errs) > 0 {
   180  		err = fmt.Errorf("%v", errs)
   181  	}
   182  
   183  	return err
   184  }
   185  
   186  //WebHandler处理所有非API请求,只需扁平化并返回仪表板网站。
   187  func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
   188  	log.Debug("Request", "URL", r.URL)
   189  
   190  	path := r.URL.String()
   191  	if path == "/" {
   192  		path = "/index.html"
   193  	}
   194  	blob, err := Asset(path[1:])
   195  	if err != nil {
   196  		log.Warn("Failed to load the asset", "path", path, "err", err)
   197  		http.Error(w, "not found", http.StatusNotFound)
   198  		return
   199  	}
   200  	w.Write(blob)
   201  }
   202  
   203  //apiHandler处理仪表板的请求。
   204  func (db *Dashboard) apiHandler(conn *websocket.Conn) {
   205  	id := atomic.AddUint32(&nextID, 1)
   206  	client := &client{
   207  		conn:   conn,
   208  		msg:    make(chan *Message, 128),
   209  		logger: log.New("id", id),
   210  	}
   211  	done := make(chan struct{})
   212  
   213  //开始侦听要发送的消息。
   214  	db.wg.Add(1)
   215  	go func() {
   216  		defer db.wg.Done()
   217  
   218  		for {
   219  			select {
   220  			case <-done:
   221  				return
   222  			case msg := <-client.msg:
   223  				if err := websocket.JSON.Send(client.conn, msg); err != nil {
   224  					client.logger.Warn("Failed to send the message", "msg", msg, "err", err)
   225  					client.conn.Close()
   226  					return
   227  				}
   228  			}
   229  		}
   230  	}()
   231  
   232  	db.lock.Lock()
   233  //发送过去的数据。
   234  	client.msg <- deepcopy.Copy(db.history).(*Message)
   235  //开始跟踪连接并在连接丢失时丢弃。
   236  	db.conns[id] = client
   237  	db.lock.Unlock()
   238  	defer func() {
   239  		db.lock.Lock()
   240  		delete(db.conns, id)
   241  		db.lock.Unlock()
   242  	}()
   243  	for {
   244  		r := new(Request)
   245  		if err := websocket.JSON.Receive(conn, r); err != nil {
   246  			if err != io.EOF {
   247  				client.logger.Warn("Failed to receive request", "err", err)
   248  			}
   249  			close(done)
   250  			return
   251  		}
   252  		if r.Logs != nil {
   253  			db.handleLogRequest(r.Logs, client)
   254  		}
   255  	}
   256  }
   257  
   258  //MeterCollector返回一个函数,该函数检索特定的仪表。
   259  func meterCollector(name string) func() int64 {
   260  	if metric := metrics.DefaultRegistry.Get(name); metric != nil {
   261  		m := metric.(metrics.Meter)
   262  		return func() int64 {
   263  			return m.Count()
   264  		}
   265  	}
   266  	return func() int64 {
   267  		return 0
   268  	}
   269  }
   270  
   271  //CollectData收集仪表板上绘图所需的数据。
   272  func (db *Dashboard) collectData() {
   273  	defer db.wg.Done()
   274  
   275  	systemCPUUsage := gosigar.Cpu{}
   276  	systemCPUUsage.Get()
   277  	var (
   278  		mem runtime.MemStats
   279  
   280  		collectNetworkIngress = meterCollector("p2p/InboundTraffic")
   281  		collectNetworkEgress  = meterCollector("p2p/OutboundTraffic")
   282  		collectDiskRead       = meterCollector("eth/db/chaindata/disk/read")
   283  		collectDiskWrite      = meterCollector("eth/db/chaindata/disk/write")
   284  
   285  		prevNetworkIngress = collectNetworkIngress()
   286  		prevNetworkEgress  = collectNetworkEgress()
   287  		prevProcessCPUTime = getProcessCPUTime()
   288  		prevSystemCPUUsage = systemCPUUsage
   289  		prevDiskRead       = collectDiskRead()
   290  		prevDiskWrite      = collectDiskWrite()
   291  
   292  		frequency = float64(db.config.Refresh / time.Second)
   293  		numCPU    = float64(runtime.NumCPU())
   294  	)
   295  
   296  	for {
   297  		select {
   298  		case errc := <-db.quit:
   299  			errc <- nil
   300  			return
   301  		case <-time.After(db.config.Refresh):
   302  			systemCPUUsage.Get()
   303  			var (
   304  				curNetworkIngress = collectNetworkIngress()
   305  				curNetworkEgress  = collectNetworkEgress()
   306  				curProcessCPUTime = getProcessCPUTime()
   307  				curSystemCPUUsage = systemCPUUsage
   308  				curDiskRead       = collectDiskRead()
   309  				curDiskWrite      = collectDiskWrite()
   310  
   311  				deltaNetworkIngress = float64(curNetworkIngress - prevNetworkIngress)
   312  				deltaNetworkEgress  = float64(curNetworkEgress - prevNetworkEgress)
   313  				deltaProcessCPUTime = curProcessCPUTime - prevProcessCPUTime
   314  				deltaSystemCPUUsage = curSystemCPUUsage.Delta(prevSystemCPUUsage)
   315  				deltaDiskRead       = curDiskRead - prevDiskRead
   316  				deltaDiskWrite      = curDiskWrite - prevDiskWrite
   317  			)
   318  			prevNetworkIngress = curNetworkIngress
   319  			prevNetworkEgress = curNetworkEgress
   320  			prevProcessCPUTime = curProcessCPUTime
   321  			prevSystemCPUUsage = curSystemCPUUsage
   322  			prevDiskRead = curDiskRead
   323  			prevDiskWrite = curDiskWrite
   324  
   325  			now := time.Now()
   326  
   327  			runtime.ReadMemStats(&mem)
   328  			activeMemory := &ChartEntry{
   329  				Time:  now,
   330  				Value: float64(mem.Alloc) / frequency,
   331  			}
   332  			virtualMemory := &ChartEntry{
   333  				Time:  now,
   334  				Value: float64(mem.Sys) / frequency,
   335  			}
   336  			networkIngress := &ChartEntry{
   337  				Time:  now,
   338  				Value: deltaNetworkIngress / frequency,
   339  			}
   340  			networkEgress := &ChartEntry{
   341  				Time:  now,
   342  				Value: deltaNetworkEgress / frequency,
   343  			}
   344  			processCPU := &ChartEntry{
   345  				Time:  now,
   346  				Value: deltaProcessCPUTime / frequency / numCPU * 100,
   347  			}
   348  			systemCPU := &ChartEntry{
   349  				Time:  now,
   350  				Value: float64(deltaSystemCPUUsage.Sys+deltaSystemCPUUsage.User) / frequency / numCPU,
   351  			}
   352  			diskRead := &ChartEntry{
   353  				Time:  now,
   354  				Value: float64(deltaDiskRead) / frequency,
   355  			}
   356  			diskWrite := &ChartEntry{
   357  				Time:  now,
   358  				Value: float64(deltaDiskWrite) / frequency,
   359  			}
   360  			sys := db.history.System
   361  			db.lock.Lock()
   362  			sys.ActiveMemory = append(sys.ActiveMemory[1:], activeMemory)
   363  			sys.VirtualMemory = append(sys.VirtualMemory[1:], virtualMemory)
   364  			sys.NetworkIngress = append(sys.NetworkIngress[1:], networkIngress)
   365  			sys.NetworkEgress = append(sys.NetworkEgress[1:], networkEgress)
   366  			sys.ProcessCPU = append(sys.ProcessCPU[1:], processCPU)
   367  			sys.SystemCPU = append(sys.SystemCPU[1:], systemCPU)
   368  			sys.DiskRead = append(sys.DiskRead[1:], diskRead)
   369  			sys.DiskWrite = append(sys.DiskWrite[1:], diskWrite)
   370  			db.lock.Unlock()
   371  
   372  			db.sendToAll(&Message{
   373  				System: &SystemMessage{
   374  					ActiveMemory:   ChartEntries{activeMemory},
   375  					VirtualMemory:  ChartEntries{virtualMemory},
   376  					NetworkIngress: ChartEntries{networkIngress},
   377  					NetworkEgress:  ChartEntries{networkEgress},
   378  					ProcessCPU:     ChartEntries{processCPU},
   379  					SystemCPU:      ChartEntries{systemCPU},
   380  					DiskRead:       ChartEntries{diskRead},
   381  					DiskWrite:      ChartEntries{diskWrite},
   382  				},
   383  			})
   384  		}
   385  	}
   386  }
   387  
   388  //sendToAll将给定消息发送到活动仪表板。
   389  func (db *Dashboard) sendToAll(msg *Message) {
   390  	db.lock.Lock()
   391  	for _, c := range db.conns {
   392  		select {
   393  		case c.msg <- msg:
   394  		default:
   395  			c.conn.Close()
   396  		}
   397  	}
   398  	db.lock.Unlock()
   399  }
   400