| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // Copyright 2015 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of go-ethereum. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | // GNU General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License | 
					
						
							| 
									
										
										
										
											2015-07-22 18:48:40 +02:00
										 |  |  | // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2015-07-07 02:54:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2015-06-25 15:33:26 +03:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2016-07-12 17:47:15 +02:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/cmd/utils" | 
					
						
							| 
									
										
										
										
											2016-02-02 19:06:43 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/node" | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	"github.com/ethereum/go-ethereum/rpc" | 
					
						
							|  |  |  | 	"github.com/gizak/termui" | 
					
						
							| 
									
										
										
										
											2016-06-09 11:44:42 +02:00
										 |  |  | 	"gopkg.in/urfave/cli.v1" | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	monitorCommandAttachFlag = cli.StringFlag{ | 
					
						
							|  |  |  | 		Name:  "attach", | 
					
						
							| 
									
										
										
										
											2016-08-18 13:28:17 +02:00
										 |  |  | 		Value: node.DefaultIPCEndpoint(clientIdentifier), | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 		Usage: "API endpoint to attach to", | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	monitorCommandRowsFlag = cli.IntFlag{ | 
					
						
							|  |  |  | 		Name:  "rows", | 
					
						
							|  |  |  | 		Value: 5, | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 		Usage: "Maximum rows in the chart grid", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	monitorCommandRefreshFlag = cli.IntFlag{ | 
					
						
							|  |  |  | 		Name:  "refresh", | 
					
						
							|  |  |  | 		Value: 3, | 
					
						
							|  |  |  | 		Usage: "Refresh interval in seconds", | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	monitorCommand = cli.Command{ | 
					
						
							| 
									
										
										
										
											2016-11-10 19:00:09 +08:00
										 |  |  | 		Action:    monitor, | 
					
						
							|  |  |  | 		Name:      "monitor", | 
					
						
							|  |  |  | 		Usage:     "Monitor and visualize node metrics", | 
					
						
							|  |  |  | 		ArgsUsage: " ", | 
					
						
							|  |  |  | 		Category:  "MONITOR COMMANDS", | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 		Description: ` | 
					
						
							|  |  |  | The Geth monitor is a tool to collect and visualize various internal metrics | 
					
						
							|  |  |  | gathered by the node, supporting different chart types as well as the capacity | 
					
						
							|  |  |  | to display multiple metrics simultaneously. | 
					
						
							|  |  |  | `, | 
					
						
							|  |  |  | 		Flags: []cli.Flag{ | 
					
						
							|  |  |  | 			monitorCommandAttachFlag, | 
					
						
							|  |  |  | 			monitorCommandRowsFlag, | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 			monitorCommandRefreshFlag, | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | // monitor starts a terminal UI based monitoring tool for the requested metrics. | 
					
						
							| 
									
										
										
										
											2016-06-10 11:23:00 +03:00
										 |  |  | func monitor(ctx *cli.Context) error { | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	var ( | 
					
						
							| 
									
										
										
										
											2016-07-12 17:47:15 +02:00
										 |  |  | 		client *rpc.Client | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 		err    error | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	// Attach to an Ethereum node over IPC or RPC | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 	endpoint := ctx.String(monitorCommandAttachFlag.Name) | 
					
						
							| 
									
										
										
										
											2016-07-12 17:47:15 +02:00
										 |  |  | 	if client, err = dialRPC(endpoint); err != nil { | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 		utils.Fatalf("Unable to attach to geth node: %v", err) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	defer client.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Retrieve all the available metrics and resolve the user pattens | 
					
						
							| 
									
										
										
										
											2015-12-16 10:58:01 +01:00
										 |  |  | 	metrics, err := retrieveMetrics(client) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		utils.Fatalf("Failed to retrieve system metrics: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 	monitored := resolveMetrics(metrics, ctx.Args()) | 
					
						
							| 
									
										
										
										
											2015-06-25 11:42:45 +03:00
										 |  |  | 	if len(monitored) == 0 { | 
					
						
							| 
									
										
										
										
											2015-06-25 15:33:26 +03:00
										 |  |  | 		list := expandMetrics(metrics, "") | 
					
						
							| 
									
										
										
										
											2015-06-25 11:42:45 +03:00
										 |  |  | 		sort.Strings(list) | 
					
						
							| 
									
										
										
										
											2015-06-29 16:11:01 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if len(list) > 0 { | 
					
						
							|  |  |  | 			utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2015-06-29 16:18:34 +03:00
										 |  |  | 			utils.Fatalf("No metrics collected by geth (--%s).\n", utils.MetricsEnabledFlag.Name) | 
					
						
							| 
									
										
										
										
											2015-06-29 16:11:01 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-25 11:42:45 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	sort.Strings(monitored) | 
					
						
							| 
									
										
										
										
											2015-06-25 12:12:11 +03:00
										 |  |  | 	if cols := len(monitored) / ctx.Int(monitorCommandRowsFlag.Name); cols > 6 { | 
					
						
							|  |  |  | 		utils.Fatalf("Requested metrics (%d) spans more that 6 columns:\n - %s", len(monitored), strings.Join(monitored, "\n - ")) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	// Create and configure the chart UI defaults | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	if err := termui.Init(); err != nil { | 
					
						
							|  |  |  | 		utils.Fatalf("Unable to initialize terminal UI: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	defer termui.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 	rows := len(monitored) | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | 	if max := ctx.Int(monitorCommandRowsFlag.Name); rows > max { | 
					
						
							|  |  |  | 		rows = max | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	cols := (len(monitored) + rows - 1) / rows | 
					
						
							|  |  |  | 	for i := 0; i < rows; i++ { | 
					
						
							|  |  |  | 		termui.Body.AddRows(termui.NewRow()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Create each individual data chart | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	footer := termui.NewPar("") | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	footer.Block.Border = true | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	footer.Height = 3 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	charts := make([]*termui.LineChart, len(monitored)) | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	units := make([]int, len(monitored)) | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	data := make([][]float64, len(monitored)) | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	for i := 0; i < len(monitored); i++ { | 
					
						
							|  |  |  | 		charts[i] = createChart((termui.TermHeight() - footer.Height) / rows) | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 		row := termui.Body.Rows[i%rows] | 
					
						
							|  |  |  | 		row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 10:58:01 +01:00
										 |  |  | 	refreshCharts(client, monitored, data, units, charts, ctx, footer) | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	termui.Body.Align() | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	termui.Render(termui.Body) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Watch for various system events, and periodically refresh the charts | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	termui.Handle("/sys/kbd/C-c", func(termui.Event) { | 
					
						
							|  |  |  | 		termui.StopLoop() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	termui.Handle("/sys/wnd/resize", func(termui.Event) { | 
					
						
							|  |  |  | 		termui.Body.Width = termui.TermWidth() | 
					
						
							|  |  |  | 		for _, chart := range charts { | 
					
						
							|  |  |  | 			chart.Height = (termui.TermHeight() - footer.Height) / rows | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		termui.Body.Align() | 
					
						
							|  |  |  | 		termui.Render(termui.Body) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		tick := time.NewTicker(time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second) | 
					
						
							|  |  |  | 		for range tick.C { | 
					
						
							| 
									
										
										
										
											2015-12-16 10:58:01 +01:00
										 |  |  | 			if refreshCharts(client, monitored, data, units, charts, ctx, footer) { | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 				termui.Body.Align() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 			termui.Render(termui.Body) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 	termui.Loop() | 
					
						
							| 
									
										
										
										
											2016-06-10 11:23:00 +03:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | // retrieveMetrics contacts the attached geth node and retrieves the entire set | 
					
						
							|  |  |  | // of collected system metrics. | 
					
						
							| 
									
										
										
										
											2016-07-12 17:47:15 +02:00
										 |  |  | func retrieveMetrics(client *rpc.Client) (map[string]interface{}, error) { | 
					
						
							|  |  |  | 	var metrics map[string]interface{} | 
					
						
							|  |  |  | 	err := client.Call(&metrics, "debug_metrics", true) | 
					
						
							|  |  |  | 	return metrics, err | 
					
						
							| 
									
										
										
										
											2015-06-25 10:36:47 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | // resolveMetrics takes a list of input metric patterns, and resolves each to one | 
					
						
							|  |  |  | // or more canonical metric names. | 
					
						
							|  |  |  | func resolveMetrics(metrics map[string]interface{}, patterns []string) []string { | 
					
						
							|  |  |  | 	res := []string{} | 
					
						
							|  |  |  | 	for _, pattern := range patterns { | 
					
						
							|  |  |  | 		res = append(res, resolveMetric(metrics, pattern, "")...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return res | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // resolveMetrics takes a single of input metric pattern, and resolves it to one | 
					
						
							|  |  |  | // or more canonical metric names. | 
					
						
							|  |  |  | func resolveMetric(metrics map[string]interface{}, pattern string, path string) []string { | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 	results := []string{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If a nested metric was requested, recurse optionally branching (via comma) | 
					
						
							|  |  |  | 	parts := strings.SplitN(pattern, "/", 2) | 
					
						
							|  |  |  | 	if len(parts) > 1 { | 
					
						
							|  |  |  | 		for _, variation := range strings.Split(parts[0], ",") { | 
					
						
							|  |  |  | 			if submetrics, ok := metrics[variation].(map[string]interface{}); !ok { | 
					
						
							|  |  |  | 				utils.Fatalf("Failed to retrieve system metrics: %s", path+variation) | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				results = append(results, resolveMetric(submetrics, parts[1], path+variation+"/")...) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 		return results | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	// Depending what the last link is, return or expand | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 	for _, variation := range strings.Split(pattern, ",") { | 
					
						
							|  |  |  | 		switch metric := metrics[variation].(type) { | 
					
						
							|  |  |  | 		case float64: | 
					
						
							|  |  |  | 			// Final metric value found, return as singleton | 
					
						
							|  |  |  | 			results = append(results, path+variation) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 		case map[string]interface{}: | 
					
						
							|  |  |  | 			results = append(results, expandMetrics(metric, path+variation+"/")...) | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 17:12:38 +03:00
										 |  |  | 	return results | 
					
						
							| 
									
										
										
										
											2015-06-24 14:38:58 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // expandMetrics expands the entire tree of metrics into a flat list of paths. | 
					
						
							|  |  |  | func expandMetrics(metrics map[string]interface{}, path string) []string { | 
					
						
							|  |  |  | 	// Iterate over all fields and expand individually | 
					
						
							|  |  |  | 	list := []string{} | 
					
						
							|  |  |  | 	for name, metric := range metrics { | 
					
						
							|  |  |  | 		switch metric := metric.(type) { | 
					
						
							|  |  |  | 		case float64: | 
					
						
							|  |  |  | 			// Final metric value found, append to list | 
					
						
							|  |  |  | 			list = append(list, path+name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case map[string]interface{}: | 
					
						
							|  |  |  | 			// Tree of metrics found, expand recursively | 
					
						
							|  |  |  | 			list = append(list, expandMetrics(metric, path+name+"/")...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			utils.Fatalf("Metric pattern %s resolved to unexpected type: %v", path+name, reflect.TypeOf(metric)) | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return list | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | // fetchMetric iterates over the metrics map and retrieves a specific one. | 
					
						
							|  |  |  | func fetchMetric(metrics map[string]interface{}, metric string) float64 { | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 	parts := strings.Split(metric, "/") | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	for _, part := range parts[:len(parts)-1] { | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 		var found bool | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 		metrics, found = metrics[part].(map[string]interface{}) | 
					
						
							|  |  |  | 		if !found { | 
					
						
							|  |  |  | 			return 0 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if v, ok := metrics[parts[len(parts)-1]].(float64); ok { | 
					
						
							|  |  |  | 		return v | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // refreshCharts retrieves a next batch of metrics, and inserts all the new | 
					
						
							|  |  |  | // values into the active datasets and charts | 
					
						
							| 
									
										
										
										
											2016-07-12 17:47:15 +02:00
										 |  |  | func refreshCharts(client *rpc.Client, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) { | 
					
						
							| 
									
										
										
										
											2015-12-16 10:58:01 +01:00
										 |  |  | 	values, err := retrieveMetrics(client) | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	for i, metric := range metrics { | 
					
						
							| 
									
										
										
										
											2015-06-26 22:05:49 +03:00
										 |  |  | 		if len(data) < 512 { | 
					
						
							|  |  |  | 			data[i] = append([]float64{fetchMetric(values, metric)}, data[i]...) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 		if updateChart(metric, data[i], &units[i], charts[i], err) { | 
					
						
							|  |  |  | 			realign = true | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	updateFooter(ctx, err, footer) | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	return | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | // updateChart inserts a dataset into a line chart, scaling appropriately as to | 
					
						
							|  |  |  | // not display weird labels, also updating the chart label accordingly. | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | func updateChart(metric string, data []float64, base *int, chart *termui.LineChart, err error) (realign bool) { | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 	dataUnits := []string{"", "K", "M", "G", "T", "E"} | 
					
						
							|  |  |  | 	timeUnits := []string{"ns", "µs", "ms", "s", "ks", "ms"} | 
					
						
							|  |  |  | 	colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	// Extract only part of the data that's actually visible | 
					
						
							| 
									
										
										
										
											2015-06-26 22:05:49 +03:00
										 |  |  | 	if chart.Width*2 < len(data) { | 
					
						
							|  |  |  | 		data = data[:chart.Width*2] | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	// Find the maximum value and scale under 1K | 
					
						
							| 
									
										
										
										
											2015-06-26 22:05:49 +03:00
										 |  |  | 	high := 0.0 | 
					
						
							|  |  |  | 	if len(data) > 0 { | 
					
						
							|  |  |  | 		high = data[0] | 
					
						
							|  |  |  | 		for _, value := range data[1:] { | 
					
						
							|  |  |  | 			high = math.Max(high, value) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	unit, scale := 0, 1.0 | 
					
						
							| 
									
										
										
										
											2015-08-25 13:57:49 +03:00
										 |  |  | 	for high >= 1000 && unit+1 < len(dataUnits) { | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 		high, unit, scale = high/1000, unit+1, scale*1000 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	// If the unit changes, re-create the chart (hack to set max height...) | 
					
						
							|  |  |  | 	if unit != *base { | 
					
						
							|  |  |  | 		realign, *base, *chart = true, unit, *createChart(chart.Height) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	// Update the chart's data points with the scaled values | 
					
						
							| 
									
										
										
										
											2015-06-26 22:05:49 +03:00
										 |  |  | 	if cap(chart.Data) < len(data) { | 
					
						
							|  |  |  | 		chart.Data = make([]float64, len(data)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	chart.Data = chart.Data[:len(data)] | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	for i, value := range data { | 
					
						
							|  |  |  | 		chart.Data[i] = value / scale | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Update the chart's label with the scale units | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 	units := dataUnits | 
					
						
							| 
									
										
										
										
											2015-06-27 20:03:31 +03:00
										 |  |  | 	if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") || strings.Contains(metric, "/time/") { | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 		units = timeUnits | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	chart.BorderLabel = metric | 
					
						
							| 
									
										
										
										
											2015-06-24 18:30:00 +03:00
										 |  |  | 	if len(units[unit]) > 0 { | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 		chart.BorderLabel += " [" + units[unit] + "]" | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 	chart.LineColor = colors[unit] | termui.AttrBold | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		chart.LineColor = termui.ColorRed | termui.AttrBold | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // createChart creates an empty line chart with the default configs. | 
					
						
							|  |  |  | func createChart(height int) *termui.LineChart { | 
					
						
							|  |  |  | 	chart := termui.NewLineChart() | 
					
						
							|  |  |  | 	if runtime.GOOS == "windows" { | 
					
						
							|  |  |  | 		chart.Mode = "dot" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	chart.DataLabels = []string{""} | 
					
						
							|  |  |  | 	chart.Height = height | 
					
						
							|  |  |  | 	chart.AxesColor = termui.ColorWhite | 
					
						
							|  |  |  | 	chart.PaddingBottom = -2 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	chart.BorderLabelFg = chart.BorderFg | termui.AttrBold | 
					
						
							|  |  |  | 	chart.BorderFg = chart.BorderBg | 
					
						
							| 
									
										
										
										
											2015-06-26 21:48:21 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return chart | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // updateFooter updates the footer contents based on any encountered errors. | 
					
						
							|  |  |  | func updateFooter(ctx *cli.Context, err error, footer *termui.Par) { | 
					
						
							|  |  |  | 	// Generate the basic footer | 
					
						
							|  |  |  | 	refresh := time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second | 
					
						
							| 
									
										
										
										
											2015-06-25 15:33:26 +03:00
										 |  |  | 	footer.Text = fmt.Sprintf("Press Ctrl+C to quit. Refresh interval: %v.", refresh) | 
					
						
							| 
									
										
										
										
											2016-02-11 16:46:28 +02:00
										 |  |  | 	footer.TextFgColor = termui.ThemeAttr("par.fg") | termui.AttrBold | 
					
						
							| 
									
										
										
										
											2015-06-25 11:32:21 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Append any encountered errors | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		footer.Text = fmt.Sprintf("Error: %v.", err) | 
					
						
							|  |  |  | 		footer.TextFgColor = termui.ColorRed | termui.AttrBold | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-06-24 16:39:22 +03:00
										 |  |  | } |