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