From e5b820c47b9343d3801e1ebbeb4a8f40843ea87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 14:38:58 +0300 Subject: cmd/geth, rpc/api: extend metrics API, add a basic monitor command --- cmd/geth/monitorcmd.go | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 cmd/geth/monitorcmd.go (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go new file mode 100644 index 000000000..2989800f9 --- /dev/null +++ b/cmd/geth/monitorcmd.go @@ -0,0 +1,180 @@ +package main + +import ( + "reflect" + "sort" + "strings" + "time" + + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/rpc/codec" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/gizak/termui" +) + +// monitor starts a terminal UI based monitoring tool for the requested metrics. +func monitor(ctx *cli.Context) { + var ( + client comms.EthereumClient + args []string + err error + ) + // Attach to an Ethereum node over IPC or RPC + if ctx.Args().Present() { + // Try to interpret the first parameter as an endpoint + client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON) + if err == nil { + args = ctx.Args().Tail() + } + } + if !ctx.Args().Present() || err != nil { + // Either no args were given, or not endpoint, use defaults + cfg := comms.IpcConfig{ + Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name), + } + args = ctx.Args() + client, err = comms.NewIpcClient(cfg, codec.JSON) + } + if err != nil { + utils.Fatalf("Unable to attach to geth node - %v", err) + } + defer client.Close() + + xeth := rpc.NewXeth(client) + + // Retrieve all the available metrics and resolve the user pattens + metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + if err != nil { + utils.Fatalf("Failed to retrieve system metrics: %v", err) + } + monitored := resolveMetrics(metrics, args) + sort.Strings(monitored) + + // Create the access function and check that the metric exists + value := func(metrics map[string]interface{}, metric string) float64 { + parts, found := strings.Split(metric, "/"), true + for _, part := range parts[:len(parts)-1] { + metrics, found = metrics[part].(map[string]interface{}) + if !found { + utils.Fatalf("Metric not found: %s", metric) + } + } + if v, ok := metrics[parts[len(parts)-1]].(float64); ok { + return v + } + utils.Fatalf("Metric not float64: %s", metric) + return 0 + } + // Assemble the terminal UI + if err := termui.Init(); err != nil { + utils.Fatalf("Unable to initialize terminal UI: %v", err) + } + defer termui.Close() + + termui.UseTheme("helloworld") + + charts := make([]*termui.LineChart, len(monitored)) + for i, metric := range monitored { + charts[i] = termui.NewLineChart() + charts[i].Border.Label = metric + charts[i].Data = make([]float64, 512) + charts[i].DataLabels = []string{""} + charts[i].Height = termui.TermHeight() / len(monitored) + charts[i].AxesColor = termui.ColorWhite + charts[i].LineColor = termui.ColorGreen + + termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, charts[i]))) + } + termui.Body.Align() + termui.Render(termui.Body) + + refresh := time.Tick(time.Second) + for { + select { + case event := <-termui.EventCh(): + if event.Type == termui.EventKey && event.Ch == 'q' { + return + } + if event.Type == termui.EventResize { + termui.Body.Width = termui.TermWidth() + for _, chart := range charts { + chart.Height = termui.TermHeight() / len(monitored) + } + termui.Body.Align() + termui.Render(termui.Body) + } + case <-refresh: + metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + if err != nil { + utils.Fatalf("Failed to retrieve system metrics: %v", err) + } + for i, metric := range monitored { + charts[i].Data = append([]float64{value(metrics, metric)}, charts[i].Data[:len(charts[i].Data)-1]...) + } + termui.Render(termui.Body) + } + } +} + +// 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 { + var ok bool + + // Build up the canonical metric path + parts := strings.Split(pattern, "/") + for len(parts) > 1 { + if metrics, ok = metrics[parts[0]].(map[string]interface{}); !ok { + utils.Fatalf("Failed to retrieve system metrics: %s", path+parts[0]) + } + path += parts[0] + "/" + parts = parts[1:] + } + // Depending what the last link is, return or expand + switch metric := metrics[parts[0]].(type) { + case float64: + // Final metric value found, return as singleton + return []string{path + parts[0]} + + case map[string]interface{}: + return expandMetrics(metric, path+parts[0]+"/") + + default: + utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) + return nil + } +} + +// 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 +} -- cgit v1.2.3 From bf99d5b33c716ecb8b7dac8234df07b0ea82c48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 16:39:22 +0300 Subject: cmd/geth: polish the monitoring charts a bit --- cmd/geth/monitorcmd.go | 57 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 6 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 2989800f9..06ccf90be 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -1,6 +1,7 @@ package main import ( + "math" "reflect" "sort" "strings" @@ -67,7 +68,7 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Metric not float64: %s", metric) return 0 } - // Assemble the terminal UI + // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) } @@ -75,17 +76,33 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") + rows := 5 + cols := (len(monitored) + rows - 1) / rows + for i := 0; i < rows; i++ { + termui.Body.AddRows(termui.NewRow()) + } + // Create each individual data chart charts := make([]*termui.LineChart, len(monitored)) + data := make([][]float64, len(monitored)) + for i := 0; i < len(data); i++ { + data[i] = make([]float64, 512) + } for i, metric := range monitored { charts[i] = termui.NewLineChart() - charts[i].Border.Label = metric + charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} - charts[i].Height = termui.TermHeight() / len(monitored) + charts[i].Height = termui.TermHeight() / rows charts[i].AxesColor = termui.ColorWhite charts[i].LineColor = termui.ColorGreen + charts[i].PaddingBottom = -1 - termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, charts[i]))) + charts[i].Border.Label = metric + charts[i].Border.LabelFgColor = charts[i].Border.FgColor + charts[i].Border.FgColor = charts[i].Border.BgColor + + row := termui.Body.Rows[i%rows] + row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } termui.Body.Align() termui.Render(termui.Body) @@ -100,7 +117,7 @@ func monitor(ctx *cli.Context) { if event.Type == termui.EventResize { termui.Body.Width = termui.TermWidth() for _, chart := range charts { - chart.Height = termui.TermHeight() / len(monitored) + chart.Height = termui.TermHeight() / rows } termui.Body.Align() termui.Render(termui.Body) @@ -111,7 +128,8 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Failed to retrieve system metrics: %v", err) } for i, metric := range monitored { - charts[i].Data = append([]float64{value(metrics, metric)}, charts[i].Data[:len(charts[i].Data)-1]...) + data[i] = append([]float64{value(metrics, metric)}, data[i][:len(data[i])-1]...) + updateChart(metric, data[i], charts[i]) } termui.Render(termui.Body) } @@ -178,3 +196,30 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { } return list } + +// updateChart inserts a dataset into a line chart, scaling appropriately as to +// not display weird labels, also updating the chart label accordingly. +func updateChart(metric string, data []float64, chart *termui.LineChart) { + units := []string{"", "K", "M", "G", "T", "E", "P"} + colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed, termui.ColorRed} + + // Find the maximum value and scale under 1K + high := data[0] + for _, value := range data[1:] { + high = math.Max(high, value) + } + unit, scale := 0, 1.0 + for high >= 1000 { + high, unit, scale = high/1000, unit+1, scale*1000 + } + // Update the chart's data points with the scaled values + for i, value := range data { + chart.Data[i] = value / scale + } + // Update the chart's label with the scale units + chart.Border.Label = metric + if unit > 0 { + chart.Border.Label += " [" + units[unit] + "]" + } + chart.LineColor = colors[unit] +} -- cgit v1.2.3 From 302187ae397c5e06a9f086183d55f591f5daf588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 17:12:38 +0300 Subject: cmd/geth: allow branching metric patterns --- cmd/geth/monitorcmd.go | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 06ccf90be..ec0dfb8f2 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -149,30 +149,37 @@ func resolveMetrics(metrics map[string]interface{}, patterns []string) []string // 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 { - var ok bool - - // Build up the canonical metric path - parts := strings.Split(pattern, "/") - for len(parts) > 1 { - if metrics, ok = metrics[parts[0]].(map[string]interface{}); !ok { - utils.Fatalf("Failed to retrieve system metrics: %s", path+parts[0]) + 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+"/")...) + } } - path += parts[0] + "/" - parts = parts[1:] + return results } // Depending what the last link is, return or expand - switch metric := metrics[parts[0]].(type) { - case float64: - // Final metric value found, return as singleton - return []string{path + parts[0]} + 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) - case map[string]interface{}: - return expandMetrics(metric, path+parts[0]+"/") + case map[string]interface{}: + results = append(results, expandMetrics(metric, path+variation+"/")...) - default: - utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) - return nil + default: + utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric)) + return nil + } } + return results } // expandMetrics expands the entire tree of metrics into a flat list of paths. -- cgit v1.2.3 From 92ef33d97a437dce2d7b55f06342de388d95f575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Jun 2015 18:30:00 +0300 Subject: rpc/api, cmd/geth: retrievel all percentiles, add time units --- cmd/geth/monitorcmd.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index ec0dfb8f2..53eb61a46 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -76,7 +76,10 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") - rows := 5 + rows := len(monitored) + if rows > 5 { + rows = 5 + } cols := (len(monitored) + rows - 1) / rows for i := 0; i < rows; i++ { termui.Body.AddRows(termui.NewRow()) @@ -207,8 +210,9 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { // updateChart inserts a dataset into a line chart, scaling appropriately as to // not display weird labels, also updating the chart label accordingly. func updateChart(metric string, data []float64, chart *termui.LineChart) { - units := []string{"", "K", "M", "G", "T", "E", "P"} - colors := []termui.Attribute{termui.ColorBlue, termui.ColorCyan, termui.ColorGreen, termui.ColorYellow, termui.ColorRed, termui.ColorRed, termui.ColorRed} + 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} // Find the maximum value and scale under 1K high := data[0] @@ -225,7 +229,12 @@ func updateChart(metric string, data []float64, chart *termui.LineChart) { } // Update the chart's label with the scale units chart.Border.Label = metric - if unit > 0 { + + units := dataUnits + if strings.Contains(metric, "Percentiles") { + units = timeUnits + } + if len(units[unit]) > 0 { chart.Border.Label += " [" + units[unit] + "]" } chart.LineColor = colors[unit] -- cgit v1.2.3 From b98b444179ed16d54fab1ff416cf561427151f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 10:36:47 +0300 Subject: cmd/geth: add attach and rows flags to the monitor command --- cmd/geth/monitorcmd.go | 65 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 23 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 53eb61a46..7b0076860 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -9,48 +9,61 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc/codec" "github.com/ethereum/go-ethereum/rpc/comms" "github.com/gizak/termui" ) +var ( + monitorCommandAttachFlag = cli.StringFlag{ + Name: "attach", + Value: "ipc:" + common.DefaultIpcPath(), + Usage: "IPC or RPC API endpoint to attach to", + } + monitorCommandRowsFlag = cli.IntFlag{ + Name: "rows", + Value: 5, + Usage: "Rows (maximum) to display the charts in", + } + monitorCommand = cli.Command{ + Action: monitor, + Name: "monitor", + Usage: `Geth Monitor: node metrics monitoring and visualization`, + 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, + }, + } +) + // monitor starts a terminal UI based monitoring tool for the requested metrics. func monitor(ctx *cli.Context) { var ( client comms.EthereumClient - args []string err error ) // Attach to an Ethereum node over IPC or RPC - if ctx.Args().Present() { - // Try to interpret the first parameter as an endpoint - client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON) - if err == nil { - args = ctx.Args().Tail() - } - } - if !ctx.Args().Present() || err != nil { - // Either no args were given, or not endpoint, use defaults - cfg := comms.IpcConfig{ - Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name), - } - args = ctx.Args() - client, err = comms.NewIpcClient(cfg, codec.JSON) - } - if err != nil { - utils.Fatalf("Unable to attach to geth node - %v", err) + endpoint := ctx.String(monitorCommandAttachFlag.Name) + if client, err = comms.ClientFromEndpoint(endpoint, codec.JSON); err != nil { + utils.Fatalf("Unable to attach to geth node: %v", err) } defer client.Close() xeth := rpc.NewXeth(client) // Retrieve all the available metrics and resolve the user pattens - metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + metrics, err := retrieveMetrics(xeth) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } - monitored := resolveMetrics(metrics, args) + monitored := resolveMetrics(metrics, ctx.Args()) sort.Strings(monitored) // Create the access function and check that the metric exists @@ -77,8 +90,8 @@ func monitor(ctx *cli.Context) { termui.UseTheme("helloworld") rows := len(monitored) - if rows > 5 { - rows = 5 + if max := ctx.Int(monitorCommandRowsFlag.Name); rows > max { + rows = max } cols := (len(monitored) + rows - 1) / rows for i := 0; i < rows; i++ { @@ -126,7 +139,7 @@ func monitor(ctx *cli.Context) { termui.Render(termui.Body) } case <-refresh: - metrics, err := xeth.Call("debug_metrics", []interface{}{true}) + metrics, err := retrieveMetrics(xeth) if err != nil { utils.Fatalf("Failed to retrieve system metrics: %v", err) } @@ -139,6 +152,12 @@ func monitor(ctx *cli.Context) { } } +// retrieveMetrics contacts the attached geth node and retrieves the entire set +// of collected system metrics. +func retrieveMetrics(xeth *rpc.Xeth) (map[string]interface{}, error) { + return xeth.Call("debug_metrics", []interface{}{true}) +} + // 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 { -- cgit v1.2.3 From d02f07a983ef4b42bfda86bd46c200bb4104e922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 11:32:21 +0300 Subject: cmd/geth: polish monitor visuals, add footer, refresh flag --- cmd/geth/monitorcmd.go | 104 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 33 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 7b0076860..492865d0e 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "math" "reflect" "sort" @@ -20,12 +21,17 @@ var ( monitorCommandAttachFlag = cli.StringFlag{ Name: "attach", Value: "ipc:" + common.DefaultIpcPath(), - Usage: "IPC or RPC API endpoint to attach to", + Usage: "API endpoint to attach to", } monitorCommandRowsFlag = cli.IntFlag{ Name: "rows", Value: 5, - Usage: "Rows (maximum) to display the charts in", + Usage: "Maximum rows in the chart grid", + } + monitorCommandRefreshFlag = cli.IntFlag{ + Name: "refresh", + Value: 3, + Usage: "Refresh interval in seconds", } monitorCommand = cli.Command{ Action: monitor, @@ -39,6 +45,7 @@ to display multiple metrics simultaneously. Flags: []cli.Flag{ monitorCommandAttachFlag, monitorCommandRowsFlag, + monitorCommandRefreshFlag, }, } ) @@ -66,21 +73,6 @@ func monitor(ctx *cli.Context) { monitored := resolveMetrics(metrics, ctx.Args()) sort.Strings(monitored) - // Create the access function and check that the metric exists - value := func(metrics map[string]interface{}, metric string) float64 { - parts, found := strings.Split(metric, "/"), true - for _, part := range parts[:len(parts)-1] { - metrics, found = metrics[part].(map[string]interface{}) - if !found { - utils.Fatalf("Metric not found: %s", metric) - } - } - if v, ok := metrics[parts[len(parts)-1]].(float64); ok { - return v - } - utils.Fatalf("Metric not float64: %s", metric) - return 0 - } // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) @@ -98,6 +90,10 @@ func monitor(ctx *cli.Context) { termui.Body.AddRows(termui.NewRow()) } // Create each individual data chart + footer := termui.NewPar("") + footer.HasBorder = true + footer.Height = 3 + charts := make([]*termui.LineChart, len(monitored)) data := make([][]float64, len(monitored)) for i := 0; i < len(data); i++ { @@ -105,25 +101,28 @@ func monitor(ctx *cli.Context) { } for i, metric := range monitored { charts[i] = termui.NewLineChart() - charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} - charts[i].Height = termui.TermHeight() / rows + charts[i].Height = (termui.TermHeight() - footer.Height) / rows charts[i].AxesColor = termui.ColorWhite - charts[i].LineColor = termui.ColorGreen charts[i].PaddingBottom = -1 charts[i].Border.Label = metric - charts[i].Border.LabelFgColor = charts[i].Border.FgColor + charts[i].Border.LabelFgColor = charts[i].Border.FgColor | termui.AttrBold charts[i].Border.FgColor = charts[i].Border.BgColor row := termui.Body.Rows[i%rows] row.Cols = append(row.Cols, termui.NewCol(12/cols, 0, charts[i])) } + termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer))) termui.Body.Align() termui.Render(termui.Body) - refresh := time.Tick(time.Second) + refreshCharts(xeth, monitored, data, charts, ctx, footer) + termui.Render(termui.Body) + + // Watch for various system events, and periodically refresh the charts + refresh := time.Tick(time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second) for { select { case event := <-termui.EventCh(): @@ -133,20 +132,13 @@ func monitor(ctx *cli.Context) { if event.Type == termui.EventResize { termui.Body.Width = termui.TermWidth() for _, chart := range charts { - chart.Height = termui.TermHeight() / rows + chart.Height = (termui.TermHeight() - footer.Height) / rows } termui.Body.Align() termui.Render(termui.Body) } case <-refresh: - metrics, err := retrieveMetrics(xeth) - if err != nil { - utils.Fatalf("Failed to retrieve system metrics: %v", err) - } - for i, metric := range monitored { - data[i] = append([]float64{value(metrics, metric)}, data[i][:len(data[i])-1]...) - updateChart(metric, data[i], charts[i]) - } + refreshCharts(xeth, monitored, data, charts, ctx, footer) termui.Render(termui.Body) } } @@ -226,13 +218,42 @@ func expandMetrics(metrics map[string]interface{}, path string) []string { return list } +// fetchMetric iterates over the metrics map and retrieves a specific one. +func fetchMetric(metrics map[string]interface{}, metric string) float64 { + parts, found := strings.Split(metric, "/"), true + for _, part := range parts[:len(parts)-1] { + 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 +func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) { + values, err := retrieveMetrics(xeth) + for i, metric := range metrics { + data[i] = append([]float64{fetchMetric(values, metric)}, data[i][:len(data[i])-1]...) + updateChart(metric, data[i], charts[i], err) + } + updateFooter(ctx, err, footer) +} + // updateChart inserts a dataset into a line chart, scaling appropriately as to // not display weird labels, also updating the chart label accordingly. -func updateChart(metric string, data []float64, chart *termui.LineChart) { +func updateChart(metric string, data []float64, chart *termui.LineChart, err error) { 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} + // Extract only part of the data that's actually visible + data = data[:chart.Width*2] + // Find the maximum value and scale under 1K high := data[0] for _, value := range data[1:] { @@ -256,5 +277,22 @@ func updateChart(metric string, data []float64, chart *termui.LineChart) { if len(units[unit]) > 0 { chart.Border.Label += " [" + units[unit] + "]" } - chart.LineColor = colors[unit] + chart.LineColor = colors[unit] | termui.AttrBold + if err != nil { + chart.LineColor = termui.ColorRed | termui.AttrBold + } +} + +// 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 + footer.Text = fmt.Sprintf("Press q to quit. Refresh interval: %v.", refresh) + footer.TextFgColor = termui.Theme().ParTextFg | termui.AttrBold + + // Append any encountered errors + if err != nil { + footer.Text = fmt.Sprintf("Error: %v.", err) + footer.TextFgColor = termui.ColorRed | termui.AttrBold + } } -- cgit v1.2.3 From 3ea6b5ae32215daa6393e02682d6946a2aa89af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 11:42:45 +0300 Subject: cmd/geth: list the available metrics if none specified --- cmd/geth/monitorcmd.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 492865d0e..b51da91e6 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -71,6 +71,19 @@ func monitor(ctx *cli.Context) { utils.Fatalf("Failed to retrieve system metrics: %v", err) } monitored := resolveMetrics(metrics, ctx.Args()) + if len(monitored) == 0 { + list := []string{} + for _, metric := range expandMetrics(metrics, "") { + switch { + case strings.HasSuffix(metric, "/0"): + list = append(list, strings.Replace(metric, "/0", "/[0-100]", -1)) + case !strings.Contains(metric, "Percentiles"): + list = append(list, metric) + } + } + sort.Strings(list) + utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) + } sort.Strings(monitored) // Create and configure the chart UI defaults -- cgit v1.2.3 From c6e2af14c0019c43c11e03b9fd79ba4489a38bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 12:12:11 +0300 Subject: cmd/geth: limit the maximum chart colums to 6 --- cmd/geth/monitorcmd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index b51da91e6..78b67c17c 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -85,7 +85,9 @@ func monitor(ctx *cli.Context) { utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } sort.Strings(monitored) - + 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 - ")) + } // Create and configure the chart UI defaults if err := termui.Init(); err != nil { utils.Fatalf("Unable to initialize terminal UI: %v", err) -- cgit v1.2.3 From fdbf8be7356cb8a80c6fdfe0d24b0863903e1832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 15:33:26 +0300 Subject: cmd/geth, rpc/api: fix reported metrics issues --- cmd/geth/monitorcmd.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 78b67c17c..43937dcaa 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "reflect" + "runtime" "sort" "strings" "time" @@ -72,15 +73,7 @@ func monitor(ctx *cli.Context) { } monitored := resolveMetrics(metrics, ctx.Args()) if len(monitored) == 0 { - list := []string{} - for _, metric := range expandMetrics(metrics, "") { - switch { - case strings.HasSuffix(metric, "/0"): - list = append(list, strings.Replace(metric, "/0", "/[0-100]", -1)) - case !strings.Contains(metric, "Percentiles"): - list = append(list, metric) - } - } + list := expandMetrics(metrics, "") sort.Strings(list) utils.Fatalf("No metrics specified.\n\nAvailable:\n - %s", strings.Join(list, "\n - ")) } @@ -116,11 +109,14 @@ func monitor(ctx *cli.Context) { } for i, metric := range monitored { charts[i] = termui.NewLineChart() + if runtime.GOOS == "windows" { + charts[i].Mode = "dot" + } charts[i].Data = make([]float64, 512) charts[i].DataLabels = []string{""} charts[i].Height = (termui.TermHeight() - footer.Height) / rows charts[i].AxesColor = termui.ColorWhite - charts[i].PaddingBottom = -1 + charts[i].PaddingBottom = -2 charts[i].Border.Label = metric charts[i].Border.LabelFgColor = charts[i].Border.FgColor | termui.AttrBold @@ -141,7 +137,7 @@ func monitor(ctx *cli.Context) { for { select { case event := <-termui.EventCh(): - if event.Type == termui.EventKey && event.Ch == 'q' { + if event.Type == termui.EventKey && event.Key == termui.KeyCtrlC { return } if event.Type == termui.EventResize { @@ -302,7 +298,7 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err func updateFooter(ctx *cli.Context, err error, footer *termui.Par) { // Generate the basic footer refresh := time.Duration(ctx.Int(monitorCommandRefreshFlag.Name)) * time.Second - footer.Text = fmt.Sprintf("Press q to quit. Refresh interval: %v.", refresh) + footer.Text = fmt.Sprintf("Press Ctrl+C to quit. Refresh interval: %v.", refresh) footer.TextFgColor = termui.Theme().ParTextFg | termui.AttrBold // Append any encountered errors -- cgit v1.2.3 From e9c0b5431cbd7430ddec9fd17983241018fd8a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Jun 2015 16:19:42 +0300 Subject: cmd/geth: finalize mem stats --- cmd/geth/monitorcmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd/geth/monitorcmd.go') diff --git a/cmd/geth/monitorcmd.go b/cmd/geth/monitorcmd.go index 43937dcaa..bb9c61a00 100644 --- a/cmd/geth/monitorcmd.go +++ b/cmd/geth/monitorcmd.go @@ -282,7 +282,7 @@ func updateChart(metric string, data []float64, chart *termui.LineChart, err err chart.Border.Label = metric units := dataUnits - if strings.Contains(metric, "Percentiles") { + if strings.Contains(metric, "/Percentiles/") || strings.Contains(metric, "/pauses/") { units = timeUnits } if len(units[unit]) > 0 { -- cgit v1.2.3