├── channelz ├── time_test.go ├── string.go ├── time.go ├── client_test.go ├── fake_client_test.go └── client.go ├── main.go ├── go.mod ├── LICENSE.txt ├── cmd ├── tree.go ├── conn.go ├── list.go ├── root.go └── describe.go ├── README.md └── go.sum /channelz/time_test.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var fixedTime = time.Unix(1543700000, 123456789).UTC() 8 | 9 | func init() { 10 | timeNow = func() time.Time { 11 | return fixedTime 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/kazegusuri/channelzcli/cmd" 7 | ) 8 | 9 | func main() { 10 | if err := cmd.NewRootCommand(os.Stdin, os.Stdout).Command().Execute(); err != nil { 11 | os.Exit(1) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /channelz/string.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "strings" 5 | 6 | channelzpb "google.golang.org/grpc/channelz/grpc_channelz_v1" 7 | ) 8 | 9 | func decorateEmpty(s string) string { 10 | if s == "" { 11 | return "" 12 | } 13 | return s 14 | } 15 | 16 | func prettyChannelTraceEventSeverity(s channelzpb.ChannelTraceEvent_Severity) string { 17 | return strings.TrimPrefix(s.String(), "CT_") 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kazegusuri/channelzcli 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/spf13/cobra v1.5.0 7 | google.golang.org/grpc v1.48.0 8 | google.golang.org/protobuf v1.28.1 9 | ) 10 | 11 | require google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 12 | 13 | require ( 14 | github.com/golang/protobuf v1.5.2 // indirect 15 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 16 | github.com/spf13/pflag v1.0.5 // indirect 17 | golang.org/x/net v0.8.0 // indirect 18 | golang.org/x/sys v0.6.0 // indirect 19 | golang.org/x/text v0.8.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Masahiro Sano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /channelz/time.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "google.golang.org/protobuf/types/known/timestamppb" 8 | ) 9 | 10 | func stringTimestamp(ts *timestamppb.Timestamp) string { 11 | if ts != nil && ts.Seconds == 0 && ts.Nanos == 0 { 12 | return "none" 13 | } 14 | 15 | return ts.AsTime().UTC().String() 16 | } 17 | 18 | func elapsedTimestamp(now time.Time, ts *timestamppb.Timestamp) string { 19 | if ts != nil && ts.Seconds == 0 && ts.Nanos == 0 { 20 | return "none" 21 | } 22 | 23 | return prettyDuration(now.Sub(ts.AsTime())) 24 | } 25 | 26 | func prettyDuration(d time.Duration) string { 27 | if d < 0 { 28 | d = -d 29 | } 30 | 31 | if d > (24 * time.Hour) { 32 | v := d / (24 * time.Hour) 33 | return fmt.Sprintf("%dd", v) 34 | } else if d > time.Hour { 35 | v := d / time.Hour 36 | return fmt.Sprintf("%dh", v) 37 | } else if d > time.Minute { 38 | v := d / time.Minute 39 | return fmt.Sprintf("%dm", v) 40 | } else if d > time.Second { 41 | v := d / time.Second 42 | return fmt.Sprintf("%ds", v) 43 | } else { 44 | v := d / time.Millisecond 45 | return fmt.Sprintf("%dms", v) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cmd/tree.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/kazegusuri/channelzcli/channelz" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | type TreeCommand struct { 12 | cmd *cobra.Command 13 | opts *GlobalOptions 14 | addr string 15 | long bool 16 | full bool 17 | } 18 | 19 | func NewTreeCommand(opts *GlobalOptions) *TreeCommand { 20 | c := &TreeCommand{ 21 | cmd: &cobra.Command{ 22 | Use: "tree (channel|server)", 23 | Short: "tree (channel|server)", 24 | Args: cobra.ExactArgs(1), 25 | SilenceUsage: true, 26 | }, 27 | opts: opts, 28 | } 29 | c.cmd.RunE = c.Run 30 | return c 31 | } 32 | 33 | func (c *TreeCommand) Command() *cobra.Command { 34 | return c.cmd 35 | } 36 | 37 | func (c *TreeCommand) Run(cmd *cobra.Command, args []string) error { 38 | ctx := context.Background() 39 | typ := args[0] 40 | 41 | conn, err := newGRPCConnection(ctx, c.opts.Address, c.opts.Insecure, c.opts.TLSData) 42 | if err != nil { 43 | return err 44 | } 45 | defer conn.Close() 46 | 47 | cc := channelz.NewClient(conn, c.opts.Output) 48 | defer cc.Flush() 49 | 50 | switch typ { 51 | case "channel": 52 | cc.TreeTopChannels(ctx) 53 | case "server": 54 | cc.TreeServers(ctx) 55 | default: 56 | c.cmd.Usage() 57 | os.Exit(1) 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /cmd/conn.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials" 13 | ) 14 | 15 | type TLSData struct { 16 | CAPool string 17 | ClientCert string 18 | ClientKey string 19 | } 20 | 21 | func newGRPCConnection(ctx context.Context, addr string, insecure bool, tlsData TLSData) (*grpc.ClientConn, error) { 22 | var dialOpts []grpc.DialOption 23 | if insecure { 24 | dialOpts = append(dialOpts, grpc.WithInsecure()) 25 | } else { 26 | var ca *x509.CertPool 27 | var err error 28 | if tlsData.CAPool == "" { 29 | ca, err = x509.SystemCertPool() 30 | if err != nil { 31 | return nil, fmt.Errorf("can't load system cert pool: %v", err) 32 | } 33 | } else { 34 | c, err := os.ReadFile(tlsData.CAPool) 35 | if err != nil { 36 | return nil, fmt.Errorf("could not read CA from %q: %v", tlsData.CAPool, err) 37 | } 38 | ca = x509.NewCertPool() 39 | if !ca.AppendCertsFromPEM(c) { 40 | return nil, fmt.Errorf("could not add CA cert to pool: %v", err) 41 | } 42 | } 43 | cert, err := tls.LoadX509KeyPair(tlsData.ClientCert, tlsData.ClientKey) 44 | if err != nil { 45 | return nil, fmt.Errorf("can't load client cert: %v", err) 46 | } 47 | dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ 48 | Certificates: []tls.Certificate{cert}, 49 | RootCAs: ca, 50 | MinVersion: tls.VersionTLS13, 51 | }))) 52 | } 53 | 54 | dialOpts = append(dialOpts, grpc.WithBlock(), grpc.WithBackoffMaxDelay(time.Second)) 55 | return grpc.DialContext(ctx, addr, dialOpts...) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/kazegusuri/channelzcli/channelz" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type ListCommand struct { 14 | cmd *cobra.Command 15 | opts *GlobalOptions 16 | addr string 17 | long bool 18 | full bool 19 | } 20 | 21 | func NewListCommand(opts *GlobalOptions) *ListCommand { 22 | c := &ListCommand{ 23 | cmd: &cobra.Command{ 24 | Use: "list (channel|server|serversocket)", 25 | Short: "list (channel|server|serversocket)", 26 | Args: cobra.ExactArgs(1), 27 | Aliases: []string{"ls"}, 28 | SilenceUsage: true, 29 | }, 30 | opts: opts, 31 | } 32 | c.cmd.RunE = c.Run 33 | return c 34 | } 35 | 36 | func (c *ListCommand) Command() *cobra.Command { 37 | return c.cmd 38 | } 39 | 40 | func (c *ListCommand) Run(cmd *cobra.Command, args []string) error { 41 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 42 | defer cancel() 43 | typ := args[0] 44 | 45 | dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 46 | defer cancel() 47 | conn, err := newGRPCConnection(dialCtx, c.opts.Address, c.opts.Insecure, c.opts.TLSData) 48 | if err != nil { 49 | return fmt.Errorf("failed to connect %v: %v", c.opts.Address, err) 50 | } 51 | defer conn.Close() 52 | 53 | cc := channelz.NewClient(conn, c.opts.Output) 54 | defer cc.Flush() 55 | 56 | switch typ { 57 | case "channel": 58 | cc.ListTopChannels(ctx) 59 | case "server": 60 | cc.ListServers(ctx) 61 | case "serversocket": 62 | cc.ListServerSockets(ctx) 63 | default: 64 | c.cmd.Usage() 65 | os.Exit(1) 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type GlobalOptions struct { 10 | Verbose bool 11 | Address string 12 | Insecure bool 13 | TLSData TLSData 14 | Input io.Reader 15 | Output io.Writer 16 | } 17 | 18 | type RootCommand struct { 19 | cmd *cobra.Command 20 | opts *GlobalOptions 21 | } 22 | 23 | func NewRootCommand(r io.Reader, w io.Writer) *RootCommand { 24 | c := &RootCommand{ 25 | cmd: &cobra.Command{ 26 | Use: "channelzcli", 27 | Short: "cli for gRPC channelz", 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | return cmd.Help() 30 | }, 31 | }, 32 | opts: &GlobalOptions{ 33 | Input: r, 34 | Output: w, 35 | }, 36 | } 37 | c.cmd.PersistentFlags().BoolVarP(&c.opts.Verbose, "verbose", "v", false, "verbose output") 38 | c.cmd.PersistentFlags().BoolVarP(&c.opts.Insecure, "insecure", "k", false, "with insecure") 39 | c.cmd.PersistentFlags().StringVar(&c.opts.Address, "addr", "", "address to gRPC server") 40 | c.cmd.PersistentFlags().StringVar(&c.opts.TLSData.CAPool, "ca-pool", "", "Location of CA pool to load for validating server TLS connections. If blank the system pool will be used.") 41 | c.cmd.PersistentFlags().StringVar(&c.opts.TLSData.ClientCert, "client-cert", "", "Location of the certificate to use for client TLS connections.") 42 | c.cmd.PersistentFlags().StringVar(&c.opts.TLSData.ClientKey, "client-key", "", "Location of the private key file to use with --client-cert.") 43 | c.cmd.AddCommand(NewListCommand(c.opts).Command()) 44 | c.cmd.AddCommand(NewTreeCommand(c.opts).Command()) 45 | c.cmd.AddCommand(NewDescribeCommand(c.opts).Command()) 46 | return c 47 | } 48 | 49 | func (c *RootCommand) Command() *cobra.Command { 50 | return c.cmd 51 | } 52 | -------------------------------------------------------------------------------- /cmd/describe.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/kazegusuri/channelzcli/channelz" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type DescribeCommand struct { 14 | cmd *cobra.Command 15 | opts *GlobalOptions 16 | addr string 17 | long bool 18 | full bool 19 | } 20 | 21 | func NewDescribeCommand(opts *GlobalOptions) *DescribeCommand { 22 | c := &DescribeCommand{ 23 | cmd: &cobra.Command{ 24 | Use: "describe (channel|server|serversocket) (NAME|ID)", 25 | Short: "describe (channel|server|serversocket) (NAME|ID)", 26 | Aliases: []string{"desc"}, 27 | Args: cobra.ExactArgs(2), 28 | SilenceUsage: true, 29 | }, 30 | opts: opts, 31 | } 32 | c.cmd.RunE = c.Run 33 | return c 34 | } 35 | 36 | func (c *DescribeCommand) Command() *cobra.Command { 37 | return c.cmd 38 | } 39 | 40 | func (c *DescribeCommand) Run(cmd *cobra.Command, args []string) error { 41 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 42 | defer cancel() 43 | typ := args[0] 44 | name := args[1] 45 | 46 | dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second) 47 | defer cancel() 48 | conn, err := newGRPCConnection(dialCtx, c.opts.Address, c.opts.Insecure, c.opts.TLSData) 49 | if err != nil { 50 | return fmt.Errorf("failed to connect %v: %v", c.opts.Address, err) 51 | } 52 | defer conn.Close() 53 | 54 | cc := channelz.NewClient(conn, c.opts.Output) 55 | defer cc.Flush() 56 | 57 | switch typ { 58 | case "channel": 59 | cc.DescribeChannel(ctx, name) 60 | case "server": 61 | cc.DescribeServer(ctx, name) 62 | case "serversocket": 63 | cc.DescribeServerSocket(ctx, name) 64 | default: 65 | c.cmd.Usage() 66 | os.Exit(1) 67 | } 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # channelzcli 2 | 3 | channelzcli is a command line tool for gRPC channelz service. 4 | 5 | ## Commands 6 | 7 | ### List 8 | 9 | `list` command displays a table of information about the specified type. 10 | 11 | Avaiable types: 12 | 13 | * `channel`: shows root channels 14 | * `server`: shows servers in the proccess 15 | 16 | 17 | ``` 18 | $ channelzcli -k --addr localhost:8000 list channel 19 | ID Name Channel SubChannel Calls Success Fail LastCall 20 | 1 spanner.googleapis.com:443 0 1 3444 3436 8 1m 21 | 2 spanner.googleapis.com:443 0 1 3451 3444 9 34s 22 | 3 spanner.googleapis.com:443 0 1 3315 3306 11 8s 23 | 4 spanner.googleapis.com:443 0 1 3724 3714 13 2m 24 | 28 pubsub.googleapis.com:443 0 16 0 0 0 none 25 | 29 pubsub.googleapis.com:443 0 16 40 40 0 13h 26 | ``` 27 | 28 | ``` 29 | $ channelzcli -k --addr localhost:8000 list server 30 | ID Name LocalAddr Calls Success Fail LastCall 31 | 31 2264 2262 1 410ms 32 | 35 [::]:5000 1732 1090 642 10h 33 | ``` 34 | 35 | ### Describe 36 | 37 | `describe` command displays details about the specified type. 38 | 39 | Avaiable types: 40 | 41 | * `channel` 42 | * `server` 43 | 44 | 45 | ``` 46 | $ channelzcli -k --addr localhost:8000 describe channel spanner.googleapis.com:443 47 | $ channelzcli -k --addr localhost:8000 describe channel 4 48 | ``` 49 | 50 | ``` 51 | $ channelzcli -k --addr localhost:8000 describe server foo 52 | $ channelzcli -k --addr localhost:8000 describe server 31 53 | ``` 54 | 55 | ### Tree 56 | 57 | 58 | `tree` command displays a tree of information about the specified type recursively. 59 | 60 | Available types: 61 | 62 | * `channel` 63 | * `server` 64 | 65 | 66 | ``` 67 | $ channelzcli -k --addr localhost:8000 tree channel 68 | pubsub.googleapis.com:443 (ID:28) [READY] 69 | [Calls] Started:0, Succeeded:0, Failed:0, Last:none 70 | [Subchannels] 71 | |-- pubsub.googleapis.com:443 (ID:40) [READY] 72 | [Calls]: Started:0, Succeeded:0, Failed:0, Last:none 73 | [Socket] ID:11562, Name:, RemoteName:, Local:[10.0.0.2]:47708 Remote:[172.217.26.42]:443 74 | |-- pubsub.googleapis.com:443 (ID:46) [READY] 75 | [Calls]: Started:0, Succeeded:0, Failed:0, Last:none 76 | [Socket] ID:11557, Name:, RemoteName:, Local:[10.0.0.2]:34138 Remote:[172.217.161.74]:443 77 | |-- pubsub.googleapis.com:443 (ID:41) [READY] 78 | [Calls]: Started:0, Succeeded:0, Failed:0, Last:none 79 | [Socket] ID:11552, Name:, RemoteName:, Local:[10.0.0.2]:60344 Remote:[216.58.197.138]:443 80 | |-- pubsub.googleapis.com:443 (ID:52) [READY] 81 | [Calls]: Started:0, Succeeded:0, Failed:0, Last:none 82 | [Socket] ID:11561, Name:, RemoteName:, Local:[10.0.0.2]:47706 Remote:[172.217.26.42]:443 83 | |-- pubsub.googleapis.com:443 (ID:43) [READY] 84 | [Calls]: Started:0, Succeeded:0, Failed:0, Last:none 85 | [Socket] ID:11556, Name:, RemoteName:, Local:[10.0.0.2]:34142 Remote:[172.217.161.74]:443 86 | ``` 87 | 88 | ## How to run channelz server (in Go) 89 | 90 | * Use [RegisterChannelzServiceToServer](https://godoc.org/google.golang.org/grpc/channelz/service#RegisterChannelzServiceToServer) to register channelz service to gRPC server 91 | * Require grpc-go v1.15.0 or later 92 | * It's also usefull for gRPC client only application, not serving gRPC server, to expose client metrics 93 | 94 | 95 | ```go 96 | import ( 97 | "log" 98 | "net" 99 | 100 | "google.golang.org/grpc" 101 | channelzsvc "google.golang.org/grpc/channelz/service" 102 | "google.golang.org/grpc/reflection" 103 | ) 104 | 105 | func main() { 106 | s := grpc.NewServer() 107 | reflection.Register(s) 108 | channelzsvc.RegisterChannelzServiceToServer(s) 109 | 110 | lis, err := net.Listen("tcp", ":8000") 111 | if err != nil { 112 | log.Fatalf("failed to listen: %v", err) 113 | } 114 | 115 | if err := s.Serve(lis); err != nil { 116 | log.Fatalf("err %v\n", err) 117 | } 118 | } 119 | ``` 120 | -------------------------------------------------------------------------------- /channelz/client_test.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func newTestClient1(b *bytes.Buffer) *ChannelzClient { 11 | return &ChannelzClient{ 12 | w: b, 13 | cc: fakeChannelzClient1, 14 | } 15 | } 16 | 17 | func assertOutput(t *testing.T, expected, actual string) { 18 | expected = strings.TrimSpace(expected) 19 | actual = strings.TrimSpace(actual) 20 | if expected != actual { 21 | t.Errorf("expected:\n%s\ngot:\n%s\n", expected, actual) 22 | } 23 | } 24 | 25 | func TestDescribeServer(t *testing.T) { 26 | b := &bytes.Buffer{} 27 | ctx := context.Background() 28 | c := newTestClient1(b) 29 | 30 | t.Run("server0", func(t *testing.T) { 31 | expected := ` 32 | ID: 0 33 | Name: server0 34 | Calls: 35 | Started: 100 36 | Succeeded: 90 37 | Failed: 10 38 | LastCallStarted: none 39 | ` 40 | t.Run("ByID", func(t *testing.T) { 41 | b.Reset() 42 | c.DescribeServer(ctx, "0") 43 | assertOutput(t, expected, b.String()) 44 | }) 45 | t.Run("ByName", func(t *testing.T) { 46 | b.Reset() 47 | c.DescribeServer(ctx, "server0") 48 | assertOutput(t, expected, b.String()) 49 | }) 50 | }) 51 | 52 | t.Run("server1", func(t *testing.T) { 53 | expected := ` 54 | ID: 1 55 | Name: server1 56 | Calls: 57 | Started: 110 58 | Succeeded: 99 59 | Failed: 11 60 | LastCallStarted: 2018-12-01 21:33:20.123456789 +0000 UTC 61 | ` 62 | t.Run("ByID", func(t *testing.T) { 63 | b.Reset() 64 | c.DescribeServer(ctx, "1") 65 | assertOutput(t, expected, b.String()) 66 | }) 67 | t.Run("ByName", func(t *testing.T) { 68 | b.Reset() 69 | c.DescribeServer(ctx, "server1") 70 | assertOutput(t, expected, b.String()) 71 | }) 72 | }) 73 | } 74 | 75 | func TestDescribeChannel(t *testing.T) { 76 | b := &bytes.Buffer{} 77 | ctx := context.Background() 78 | c := newTestClient1(b) 79 | 80 | t.Run("TopChannel", func(t *testing.T) { 81 | expected := ` 82 | ID: 0 83 | Name: foo0 84 | State: READY 85 | Target: foo0.test.com 86 | Calls: 87 | Started: 100 88 | Succeeded: 90 89 | Failed: 10 90 | LastCallStarted: 2018-12-01 21:33:20.123456789 +0000 UTC 91 | Socket: 92 | Channels: 93 | Subchannels: 94 | ID Name State Start Succeeded Failed 95 | 0 bar0 READY 100 90 10 96 | Trace: 97 | NumEvents: 0 98 | CreationTimestamp: none 99 | ` 100 | t.Run("ByID", func(t *testing.T) { 101 | b.Reset() 102 | c.DescribeChannel(ctx, "0") 103 | assertOutput(t, expected, b.String()) 104 | }) 105 | t.Run("ByName", func(t *testing.T) { 106 | b.Reset() 107 | c.DescribeChannel(ctx, "foo0") 108 | assertOutput(t, expected, b.String()) 109 | }) 110 | }) 111 | 112 | t.Run("TopChannelWithSubChannels", func(t *testing.T) { 113 | expected := ` 114 | ID: 1 115 | Name: foo1 116 | State: READY 117 | Target: foo1.test.com 118 | Calls: 119 | Started: 110 120 | Succeeded: 99 121 | Failed: 11 122 | LastCallStarted: 2018-12-01 21:33:20.123456789 +0000 UTC 123 | Socket: 124 | Channels: 125 | Subchannels: 126 | ID Name State Start Succeeded Failed 127 | 1 bar1 READY 110 99 11 128 | 2 bar2 READY 120 108 12 129 | 3 bar3 READY 130 117 13 130 | 4 bar4 READY 140 126 14 131 | Trace: 132 | NumEvents: 0 133 | CreationTimestamp: none 134 | ` 135 | t.Run("ByID", func(t *testing.T) { 136 | b.Reset() 137 | c.DescribeChannel(ctx, "1") 138 | assertOutput(t, expected, b.String()) 139 | }) 140 | t.Run("ByName", func(t *testing.T) { 141 | b.Reset() 142 | c.DescribeChannel(ctx, "foo1") 143 | assertOutput(t, expected, b.String()) 144 | }) 145 | }) 146 | } 147 | 148 | func TestListServers(t *testing.T) { 149 | b := &bytes.Buffer{} 150 | ctx := context.Background() 151 | c := newTestClient1(b) 152 | 153 | t.Run("server", func(t *testing.T) { 154 | expected := ` 155 | ID Name LocalAddr Calls Success Fail LastCall 156 | 0 server0 [127.0.1.2]:9000 100 90 10 none 157 | 1 server1 [127.0.1.2]:9001 110 99 11 0ms 158 | ` 159 | b.Reset() 160 | c.ListServers(ctx) 161 | assertOutput(t, expected, b.String()) 162 | }) 163 | } 164 | 165 | func TestListChannels(t *testing.T) { 166 | b := &bytes.Buffer{} 167 | ctx := context.Background() 168 | c := newTestClient1(b) 169 | 170 | t.Run("server", func(t *testing.T) { 171 | expected := ` 172 | ID Name State Channel SubChannel Calls Success Fail LastCall 173 | 0 foo0 READY 0 1 100 90 10 0ms 174 | 1 foo1 READY 0 4 110 99 11 0ms 175 | ` 176 | b.Reset() 177 | c.ListTopChannels(ctx) 178 | assertOutput(t, expected, b.String()) 179 | }) 180 | } 181 | -------------------------------------------------------------------------------- /channelz/fake_client_test.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "google.golang.org/grpc" 9 | channelzpb "google.golang.org/grpc/channelz/grpc_channelz_v1" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | "google.golang.org/protobuf/types/known/timestamppb" 13 | ) 14 | 15 | var _ channelzpb.ChannelzClient = (*fakeChannelzClient)(nil) 16 | 17 | type fakeChannelzClient struct { 18 | topChannels []*channelzpb.Channel 19 | servers []*channelzpb.Server 20 | channels []*channelzpb.Channel 21 | subchannels []*channelzpb.Subchannel 22 | sockets []*channelzpb.Socket 23 | } 24 | 25 | func (c *fakeChannelzClient) GetTopChannels(ctx context.Context, in *channelzpb.GetTopChannelsRequest, opts ...grpc.CallOption) (*channelzpb.GetTopChannelsResponse, error) { 26 | return &channelzpb.GetTopChannelsResponse{ 27 | Channel: c.topChannels, 28 | End: true, 29 | }, nil 30 | } 31 | 32 | func (c *fakeChannelzClient) GetServers(ctx context.Context, in *channelzpb.GetServersRequest, opts ...grpc.CallOption) (*channelzpb.GetServersResponse, error) { 33 | return &channelzpb.GetServersResponse{ 34 | Server: c.servers, 35 | End: true, 36 | }, nil 37 | } 38 | 39 | func (c *fakeChannelzClient) GetServer(ctx context.Context, in *channelzpb.GetServerRequest, opts ...grpc.CallOption) (*channelzpb.GetServerResponse, error) { 40 | for _, s := range c.servers { 41 | if in.ServerId == s.Ref.ServerId { 42 | return &channelzpb.GetServerResponse{ 43 | Server: s, 44 | }, nil 45 | } 46 | } 47 | return nil, status.Errorf(codes.NotFound, "not found") 48 | } 49 | 50 | func (c *fakeChannelzClient) GetServerSockets(ctx context.Context, in *channelzpb.GetServerSocketsRequest, opts ...grpc.CallOption) (*channelzpb.GetServerSocketsResponse, error) { 51 | return nil, status.Errorf(codes.Unimplemented, "not implemented") 52 | } 53 | 54 | func (c *fakeChannelzClient) GetChannel(ctx context.Context, in *channelzpb.GetChannelRequest, opts ...grpc.CallOption) (*channelzpb.GetChannelResponse, error) { 55 | for _, ch := range c.channels { 56 | if in.ChannelId == ch.Ref.ChannelId { 57 | return &channelzpb.GetChannelResponse{ 58 | Channel: ch, 59 | }, nil 60 | } 61 | } 62 | return nil, status.Errorf(codes.NotFound, "not found") 63 | } 64 | 65 | func (c *fakeChannelzClient) GetSubchannel(ctx context.Context, in *channelzpb.GetSubchannelRequest, opts ...grpc.CallOption) (*channelzpb.GetSubchannelResponse, error) { 66 | for _, ch := range c.subchannels { 67 | if in.SubchannelId == ch.Ref.SubchannelId { 68 | return &channelzpb.GetSubchannelResponse{ 69 | Subchannel: ch, 70 | }, nil 71 | } 72 | } 73 | return nil, status.Errorf(codes.NotFound, "not found") 74 | } 75 | 76 | func (c *fakeChannelzClient) GetSocket(ctx context.Context, in *channelzpb.GetSocketRequest, opts ...grpc.CallOption) (*channelzpb.GetSocketResponse, error) { 77 | for _, socket := range c.sockets { 78 | if in.SocketId == socket.Ref.SocketId { 79 | return &channelzpb.GetSocketResponse{ 80 | Socket: socket, 81 | }, nil 82 | } 83 | } 84 | return nil, status.Errorf(codes.NotFound, "not found") 85 | } 86 | 87 | var ( 88 | channelCount int64 89 | subchannelCount int64 90 | socketCount int64 91 | serverCount int64 92 | ) 93 | 94 | type channelParam struct { 95 | state channelzpb.ChannelConnectivityState_State 96 | lastCallStartedTimestamp *timestamppb.Timestamp 97 | chRef []*channelzpb.ChannelRef 98 | subchRef []*channelzpb.SubchannelRef 99 | sockRef []*channelzpb.SocketRef 100 | } 101 | 102 | type socketParam struct { 103 | remoteIP net.IP 104 | remotePort int32 105 | localIP net.IP 106 | localPort int32 107 | } 108 | 109 | type serverParam struct { 110 | lastCallStartedTimestamp *timestamppb.Timestamp 111 | sockRef []*channelzpb.SocketRef 112 | } 113 | 114 | func testCreateChannel(param channelParam) *channelzpb.Channel { 115 | id := channelCount 116 | channelCount++ 117 | return &channelzpb.Channel{ 118 | Ref: &channelzpb.ChannelRef{ 119 | ChannelId: id, 120 | Name: fmt.Sprintf("foo%d", id), 121 | }, 122 | Data: &channelzpb.ChannelData{ 123 | State: &channelzpb.ChannelConnectivityState{ 124 | State: param.state, 125 | }, 126 | Target: fmt.Sprintf("foo%d.test.com", id), 127 | Trace: &channelzpb.ChannelTrace{}, 128 | CallsStarted: 100 + id*10, 129 | CallsSucceeded: 90 + id*9, 130 | CallsFailed: 10 + id, 131 | LastCallStartedTimestamp: param.lastCallStartedTimestamp, 132 | }, 133 | ChannelRef: param.chRef, 134 | SubchannelRef: param.subchRef, 135 | SocketRef: param.sockRef, 136 | } 137 | } 138 | 139 | func testCreateSubchannel(param channelParam) *channelzpb.Subchannel { 140 | id := subchannelCount 141 | subchannelCount++ 142 | return &channelzpb.Subchannel{ 143 | Ref: &channelzpb.SubchannelRef{ 144 | SubchannelId: id, 145 | Name: fmt.Sprintf("bar%d", id), 146 | }, 147 | Data: &channelzpb.ChannelData{ 148 | State: &channelzpb.ChannelConnectivityState{ 149 | State: param.state, 150 | }, 151 | Target: fmt.Sprintf("bar%d.test.com", id), 152 | Trace: &channelzpb.ChannelTrace{}, 153 | CallsStarted: 100 + id*10, 154 | CallsSucceeded: 90 + id*9, 155 | CallsFailed: 10 + id, 156 | LastCallStartedTimestamp: param.lastCallStartedTimestamp, 157 | }, 158 | ChannelRef: param.chRef, 159 | SubchannelRef: param.subchRef, 160 | SocketRef: param.sockRef, 161 | } 162 | } 163 | 164 | func testCreateSocket(param socketParam) *channelzpb.Socket { 165 | id := socketCount 166 | socketCount++ 167 | 168 | var localAddr *channelzpb.Address 169 | var remoteAddr *channelzpb.Address 170 | 171 | if param.localIP != nil { 172 | localAddr = &channelzpb.Address{ 173 | Address: &channelzpb.Address_TcpipAddress{ 174 | TcpipAddress: &channelzpb.Address_TcpIpAddress{ 175 | IpAddress: []byte(param.localIP), 176 | Port: param.localPort, 177 | }, 178 | }, 179 | } 180 | } 181 | 182 | if param.remoteIP != nil { 183 | remoteAddr = &channelzpb.Address{ 184 | Address: &channelzpb.Address_TcpipAddress{ 185 | TcpipAddress: &channelzpb.Address_TcpIpAddress{ 186 | IpAddress: []byte(param.remoteIP), 187 | Port: param.remotePort, 188 | }, 189 | }, 190 | } 191 | } 192 | 193 | return &channelzpb.Socket{ 194 | Ref: &channelzpb.SocketRef{ 195 | SocketId: id, 196 | Name: fmt.Sprintf("sock%d", id), 197 | }, 198 | Data: &channelzpb.SocketData{}, 199 | Local: localAddr, 200 | Remote: remoteAddr, 201 | Security: &channelzpb.Security{}, 202 | RemoteName: "", 203 | } 204 | } 205 | 206 | func testCreateServer(param serverParam) *channelzpb.Server { 207 | id := serverCount 208 | serverCount++ 209 | 210 | return &channelzpb.Server{ 211 | Ref: &channelzpb.ServerRef{ 212 | ServerId: id, 213 | Name: fmt.Sprintf("server%d", id), 214 | }, 215 | Data: &channelzpb.ServerData{ 216 | CallsStarted: 100 + id*10, 217 | CallsSucceeded: 90 + id*9, 218 | CallsFailed: 10 + id, 219 | LastCallStartedTimestamp: param.lastCallStartedTimestamp, 220 | }, 221 | ListenSocket: param.sockRef, 222 | } 223 | } 224 | 225 | var ( 226 | fakeChannelzClient1 channelzpb.ChannelzClient 227 | ) 228 | 229 | func init() { 230 | now := fixedTime 231 | ts1 := timestamppb.New(now) 232 | 233 | srvsock1 := testCreateSocket(socketParam{ 234 | localIP: net.IPv4(127, 0, 1, 2), 235 | localPort: 9000, 236 | }) 237 | srvsock2 := testCreateSocket(socketParam{ 238 | localIP: net.IPv4(127, 0, 1, 2), 239 | localPort: 9001, 240 | }) 241 | srv1 := testCreateServer(serverParam{ 242 | sockRef: []*channelzpb.SocketRef{srvsock1.Ref}, 243 | }) 244 | srv2 := testCreateServer(serverParam{ 245 | lastCallStartedTimestamp: ts1, 246 | sockRef: []*channelzpb.SocketRef{srvsock2.Ref}, 247 | }) 248 | 249 | subchsock1 := testCreateSocket(socketParam{ 250 | localIP: net.IPv4(127, 0, 1, 2), 251 | localPort: 9001, 252 | remoteIP: net.IPv4(111, 111, 111, 111), 253 | remotePort: 30000, 254 | }) 255 | subch1 := testCreateSubchannel(channelParam{ 256 | state: channelzpb.ChannelConnectivityState_READY, 257 | lastCallStartedTimestamp: ts1, 258 | sockRef: []*channelzpb.SocketRef{subchsock1.Ref}, 259 | }) 260 | topch1 := testCreateChannel(channelParam{ 261 | state: channelzpb.ChannelConnectivityState_READY, 262 | lastCallStartedTimestamp: ts1, 263 | subchRef: []*channelzpb.SubchannelRef{subch1.Ref}, 264 | }) 265 | 266 | var subchSocks1 []*channelzpb.Socket 267 | var subchs1 []*channelzpb.Subchannel 268 | var subchRef1 []*channelzpb.SubchannelRef 269 | for i := 0; i < 4; i++ { 270 | socket := testCreateSocket(socketParam{ 271 | localIP: net.IPv4(127, 0, 1, 2), 272 | localPort: 9001, 273 | remoteIP: net.IPv4(111, 111, 111, byte(112+i)), 274 | remotePort: int32(30001 + i), 275 | }) 276 | subchSocks1 = append(subchSocks1, socket) 277 | subch := testCreateSubchannel(channelParam{ 278 | state: channelzpb.ChannelConnectivityState_READY, 279 | lastCallStartedTimestamp: ts1, 280 | sockRef: []*channelzpb.SocketRef{socket.Ref}, 281 | }) 282 | subchs1 = append(subchs1, subch) 283 | subchRef1 = append(subchRef1, subch.Ref) 284 | } 285 | topch2 := testCreateChannel(channelParam{ 286 | state: channelzpb.ChannelConnectivityState_READY, 287 | lastCallStartedTimestamp: ts1, 288 | subchRef: subchRef1, 289 | }) 290 | 291 | fakeChannelzClient1 = &fakeChannelzClient{ 292 | topChannels: []*channelzpb.Channel{topch1, topch2}, 293 | channels: []*channelzpb.Channel{ 294 | topch1, topch2, 295 | }, 296 | subchannels: append([]*channelzpb.Subchannel{ 297 | subch1, 298 | }, subchs1...), 299 | sockets: append([]*channelzpb.Socket{ 300 | srvsock1, srvsock2, 301 | subchsock1, 302 | }, subchSocks1...), 303 | servers: []*channelzpb.Server{srv1, srv2}, 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 8 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 9 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 10 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 11 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 12 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 13 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 17 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 18 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 19 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 20 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 21 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 22 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 23 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 24 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 28 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 29 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 30 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 31 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 32 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 33 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 34 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 35 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 36 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 37 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 38 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 39 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 40 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 41 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 42 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 43 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 44 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 46 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 47 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 48 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 49 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 50 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 53 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 54 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 55 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 56 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 57 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 58 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 59 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 60 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 61 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 62 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 63 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 64 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 65 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 66 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 67 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 68 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 69 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 70 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 71 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 72 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 73 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 74 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 75 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 76 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 77 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 78 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 79 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 80 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 81 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 82 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 83 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 84 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 85 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 86 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 87 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 89 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 92 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 94 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 95 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 96 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 97 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 98 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 99 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 100 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 101 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 102 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 106 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 107 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 108 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 109 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 110 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 111 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 112 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 113 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 114 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 115 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 116 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 117 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 118 | google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= 119 | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 120 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 121 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 122 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 123 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 124 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 125 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 126 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 127 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 128 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 129 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 130 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 131 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 132 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 133 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 134 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 135 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 136 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 137 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 138 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 139 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 140 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 141 | -------------------------------------------------------------------------------- /channelz/client.go: -------------------------------------------------------------------------------- 1 | package channelz 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "strconv" 10 | "text/tabwriter" 11 | "time" 12 | 13 | "google.golang.org/grpc" 14 | channelzpb "google.golang.org/grpc/channelz/grpc_channelz_v1" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | ) 18 | 19 | var timeNow = time.Now 20 | 21 | type ChannelzClient struct { 22 | cc channelzpb.ChannelzClient 23 | w *tabwriter.Writer 24 | } 25 | 26 | func NewClient(conn *grpc.ClientConn, w io.Writer) *ChannelzClient { 27 | return &ChannelzClient{ 28 | cc: channelzpb.NewChannelzClient(conn), 29 | w: tabwriter.NewWriter(w, 0, 8, 1, '\t', tabwriter.AlignRight), 30 | } 31 | } 32 | 33 | func (cc *ChannelzClient) Flush() error { 34 | if cc.w != nil { 35 | return cc.w.Flush() 36 | } 37 | return nil 38 | } 39 | 40 | func (cc *ChannelzClient) printf(format string, a ...interface{}) (n int, err error) { 41 | return fmt.Fprintf(cc.w, format, a...) 42 | } 43 | 44 | func (cc *ChannelzClient) DescribeServer(ctx context.Context, name string) { 45 | server := cc.findServer(ctx, name) 46 | if server == nil { 47 | fmt.Printf("server %q not found", name) 48 | return 49 | } 50 | 51 | cc.printf("ID: \t%d\n", server.Ref.ServerId) 52 | cc.printf("Name:\t%s\n", server.Ref.Name) 53 | 54 | cc.printf("Calls:\n") 55 | cc.printf(" Started: \t%d\n", server.Data.CallsStarted) 56 | cc.printf(" Succeeded: \t%d\n", server.Data.CallsSucceeded) 57 | cc.printf(" Failed: \t%d\n", server.Data.CallsFailed) 58 | cc.printf(" LastCallStarted:\t%s\n", stringTimestamp(server.Data.LastCallStartedTimestamp)) 59 | 60 | if server.Data.Trace != nil { 61 | cc.printf("Trace:\n") 62 | cc.printf(" NumEvents:\t%d\n", server.Data.Trace.NumEventsLogged) 63 | cc.printf(" CreationTimestamp:\t%s\n", stringTimestamp(server.Data.Trace.CreationTimestamp)) 64 | 65 | if len(server.Data.Trace.Events) != 0 { 66 | cc.printf(" Events\n") 67 | cc.printf(" %s\t%-80s\t%s\n", "Severity", "Description", "Timestamp") 68 | for _, ev := range server.Data.Trace.Events { 69 | cc.printf(" %s\t%-80s\t%s\n", 70 | prettyChannelTraceEventSeverity(ev.Severity), ev.Description, stringTimestamp(ev.Timestamp)) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func (cc *ChannelzClient) findServer(ctx context.Context, name string) *channelzpb.Server { 77 | n, err := strconv.Atoi(name) 78 | if err != nil { 79 | return cc.findServerByName(ctx, name) 80 | } 81 | return cc.findServerByID(ctx, int64(n)) 82 | } 83 | 84 | func (cc *ChannelzClient) findServerByName(ctx context.Context, name string) *channelzpb.Server { 85 | var found *channelzpb.Server 86 | cc.visitGetServers(ctx, func(server *channelzpb.Server) { 87 | if server.Ref.Name == name { 88 | if found == nil { 89 | found = server 90 | } 91 | } 92 | }) 93 | 94 | return found 95 | } 96 | 97 | func (cc *ChannelzClient) findServerByID(ctx context.Context, id int64) *channelzpb.Server { 98 | var found *channelzpb.Server 99 | cc.visitGetServers(ctx, func(server *channelzpb.Server) { 100 | if server.Ref.ServerId == id { 101 | found = server 102 | } 103 | }) 104 | 105 | return found 106 | } 107 | 108 | func (cc *ChannelzClient) DescribeChannel(ctx context.Context, name string) { 109 | channel := cc.findTopChannel(ctx, name) 110 | if channel == nil { 111 | cc.printf("channel %q not found\n", name) 112 | return 113 | } 114 | 115 | cc.printf("ID: \t%d\n", channel.Ref.ChannelId) 116 | cc.printf("Name: \t%s\n", channel.Ref.Name) 117 | cc.printf("State: \t%s\n", channel.Data.State.State.String()) 118 | cc.printf("Target: \t%s\n", channel.Data.Target) 119 | 120 | cc.printf("Calls:\n") 121 | cc.printf(" Started: \t%d\n", channel.Data.CallsStarted) 122 | cc.printf(" Succeeded: \t%d\n", channel.Data.CallsSucceeded) 123 | cc.printf(" Failed: \t%d\n", channel.Data.CallsFailed) 124 | cc.printf(" LastCallStarted:\t%s\n", stringTimestamp(channel.Data.LastCallStartedTimestamp)) 125 | 126 | if len(channel.SocketRef) == 0 { 127 | cc.printf("Socket: \t%s\n", "") 128 | } else { 129 | cc.printf(" Sockets\n") 130 | cc.printf(" %s\t%s\n", "SocketID", "Name") 131 | for _, socket := range channel.SocketRef { 132 | cc.printf(" %d\t%s\t\n", socket.SocketId, socket.Name) 133 | } 134 | } 135 | 136 | if len(channel.ChannelRef) == 0 { 137 | cc.printf("Channels: \t%s\n", "") 138 | } else { 139 | cc.printf("Channels\n") 140 | cc.printf(" %s\t%s\n", "SocketID", "Name") 141 | for _, channel := range channel.ChannelRef { 142 | cc.printf(" %d\t%s\n", channel.ChannelId, channel.Name) 143 | } 144 | } 145 | 146 | if len(channel.SubchannelRef) == 0 { 147 | cc.printf("Subchannels: \t%s\n", "") 148 | } else { 149 | cc.printf("Subchannels:\n") 150 | cc.printf(" %s\t%s\t%s\t%-6s\t%-8s\t%-6s\n", "ID", "Name", "State", "Start", "Succeeded", "Failed") 151 | for _, subchref := range channel.SubchannelRef { 152 | res, err := cc.cc.GetSubchannel(ctx, &channelzpb.GetSubchannelRequest{SubchannelId: subchref.SubchannelId}) 153 | if err != nil { 154 | log.Fatalf("err %v", err) 155 | } 156 | 157 | subch := res.Subchannel 158 | cc.printf(" %d\t%s\t%s\t%-6d\t%-8d\t%-6d\n", 159 | subch.Ref.SubchannelId, subch.Ref.Name, subch.Data.State.State.String(), 160 | subch.Data.CallsStarted, 161 | subch.Data.CallsSucceeded, 162 | subch.Data.CallsFailed, 163 | ) 164 | } 165 | } 166 | 167 | if channel.Data.Trace != nil { 168 | cc.printf("Trace:\n") 169 | cc.printf(" NumEvents:\t%d\n", channel.Data.Trace.NumEventsLogged) 170 | cc.printf(" CreationTimestamp:\t%s\n", stringTimestamp(channel.Data.Trace.CreationTimestamp)) 171 | 172 | if len(channel.Data.Trace.Events) != 0 { 173 | cc.printf(" Events\n") 174 | cc.printf(" %s\t%-80s\t%s\n", "Severity", "Description", "Timestamp") 175 | for _, ev := range channel.Data.Trace.Events { 176 | cc.printf(" %s\t%-80s\t%s\n", 177 | prettyChannelTraceEventSeverity(ev.Severity), ev.Description, stringTimestamp(ev.Timestamp)) 178 | } 179 | } 180 | } 181 | } 182 | 183 | func (cc *ChannelzClient) findSocketByID(ctx context.Context, id int64) *channelzpb.Socket { 184 | res, err := cc.cc.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: id}) 185 | if err != nil { 186 | if status.Code(err) == codes.NotFound { 187 | return nil 188 | } 189 | log.Fatalf("err: %v\n", err) 190 | } 191 | 192 | return res.Socket 193 | } 194 | 195 | func (cc *ChannelzClient) DescribeServerSocket(ctx context.Context, name string) { 196 | id, err := strconv.ParseInt(name, 10, 64) 197 | if err != nil { 198 | // TODO: find by name 199 | cc.printf("serversocket %q not found\n", name) 200 | return 201 | } 202 | 203 | socket := cc.findSocketByID(ctx, id) 204 | if socket == nil { 205 | cc.printf("serversocket %q not found\n", name) 206 | return 207 | } 208 | 209 | cc.printf("ID: \t%d\n", socket.Ref.SocketId) 210 | cc.printf("Name: \t%s\n", socket.Ref.Name) 211 | cc.printf("Local: \t%s\n", addrToString(socket.Local)) 212 | cc.printf("Remote: \t%s\n", addrToString(socket.Remote)) 213 | 214 | cc.printf("Streams:\n") 215 | cc.printf(" Started: \t%d\n", socket.Data.StreamsStarted) 216 | cc.printf(" Succeeded: \t%d\n", socket.Data.StreamsSucceeded) 217 | cc.printf(" Failed: \t%d\n", socket.Data.StreamsFailed) 218 | cc.printf(" LastCreated:\t%s\n", stringTimestamp(socket.Data.LastRemoteStreamCreatedTimestamp)) 219 | 220 | cc.printf("Messages:\n") 221 | cc.printf(" Sent: \t%d\n", socket.Data.MessagesSent) 222 | cc.printf(" Recieved: \t%d\n", socket.Data.MessagesReceived) 223 | cc.printf(" LastSent:\t%s\n", stringTimestamp(socket.Data.LastMessageSentTimestamp)) 224 | cc.printf(" LastReceived:\t%s\n", stringTimestamp(socket.Data.LastMessageReceivedTimestamp)) 225 | 226 | cc.printf("Options:\n") 227 | for _, opt := range socket.Data.Option { 228 | cc.printf(" %s:\t%s\n", opt.Name, opt.Value) 229 | } 230 | 231 | cc.printf("Security:\n") 232 | if socket.Security == nil { 233 | cc.printf(" Model: none\n") 234 | } else { 235 | 236 | switch socket.Security.GetModel().(type) { 237 | case *channelzpb.Security_Tls_: 238 | cc.printf(" Model: tls\n") 239 | case *channelzpb.Security_Other: 240 | cc.printf(" Model: other\n") 241 | } 242 | } 243 | } 244 | 245 | func (cc *ChannelzClient) ListServers(ctx context.Context) { 246 | now := timeNow() 247 | 248 | cc.printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 249 | "ID", "Name", "LocalAddr", "Calls", "Success", "Fail", "LastCall") 250 | 251 | cc.visitGetServers(ctx, func(server *channelzpb.Server) { 252 | // see first socket only 253 | var socket *channelzpb.Socket 254 | if len(server.ListenSocket) > 0 { 255 | res, err := cc.cc.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: server.ListenSocket[0].SocketId}) 256 | if err != nil { 257 | log.Fatalf("err %v\n", err) 258 | } 259 | socket = res.Socket 260 | } 261 | 262 | var localAddr string 263 | if addr := socket.GetLocal().GetTcpipAddress(); addr != nil { 264 | localAddr = fmt.Sprintf("[%v]:%v", net.IP(addr.IpAddress).String(), addr.Port) 265 | } 266 | 267 | cc.printf("%d\t%s\t%-12s\t%-6d\t%-6d\t%-6d\t%s\n", 268 | server.Ref.ServerId, 269 | decorateEmpty(server.Ref.Name), 270 | decorateEmpty(localAddr), 271 | server.Data.CallsStarted, 272 | server.Data.CallsSucceeded, 273 | server.Data.CallsFailed, 274 | elapsedTimestamp(now, server.Data.LastCallStartedTimestamp), 275 | ) 276 | }) 277 | } 278 | 279 | func (cc *ChannelzClient) TreeServers(ctx context.Context) { 280 | now := timeNow() 281 | cc.visitGetServers(ctx, func(server *channelzpb.Server) { 282 | cc.printf("ID: %v, Name: %v\n", server.Ref.ServerId, server.Ref.Name) 283 | 284 | elapesed := elapsedTimestamp(now, server.Data.LastCallStartedTimestamp) 285 | cc.printf(" [Calls]: Started:%v Succeeded:%v, Failed:%v, Last:%s\n", server.Data.CallsStarted, server.Data.CallsSucceeded, server.Data.CallsFailed, elapesed) 286 | 287 | for _, socket := range server.ListenSocket { 288 | res, err := cc.cc.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: socket.SocketId}) 289 | if err != nil { 290 | log.Fatalf("err %v\n", err) 291 | } 292 | 293 | socket := res.Socket 294 | if socket == nil { 295 | cc.printf("not found\n") 296 | continue 297 | } 298 | cc.printf(" [Socket] ID:%v, Name:%v, RemoteName:%v", socket.Ref.SocketId, socket.Ref.Name, socket.RemoteName) 299 | if addr := socket.Local.GetTcpipAddress(); addr != nil { 300 | cc.printf(", Local IP:%v, Port:%v", net.IP(addr.IpAddress).String(), addr.Port) 301 | } 302 | cc.printf("\n") 303 | } 304 | 305 | cc.printf("\n") 306 | }) 307 | } 308 | 309 | func (cc *ChannelzClient) visitGetServers(ctx context.Context, fn func(*channelzpb.Server)) { 310 | lastServerID := int64(0) 311 | for { 312 | res, err := cc.cc.GetServers(ctx, &channelzpb.GetServersRequest{StartServerId: lastServerID}) 313 | if err != nil { 314 | log.Fatalf("err: %v\n", err) 315 | } 316 | 317 | for _, server := range res.Server { 318 | fn(server) 319 | } 320 | if res.End { 321 | break 322 | } 323 | 324 | lastServerID++ 325 | } 326 | } 327 | 328 | func (cc *ChannelzClient) ListTopChannels(ctx context.Context) { 329 | now := timeNow() 330 | 331 | cc.printf("%s\t%-80s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 332 | "ID", "Name", "State", "Channel", "SubChannel", "Calls", "Success", "Fail", "LastCall") 333 | 334 | cc.visitTopChannels(ctx, func(channel *channelzpb.Channel) { 335 | cc.printf("%d\t%-80s\t%s\t%-7d\t%-10d\t%-6d\t%-6d\t%-6d\t%-8s\n", 336 | channel.Ref.ChannelId, 337 | decorateEmpty(channel.Ref.Name), 338 | channel.Data.State.State.String(), 339 | len(channel.ChannelRef), 340 | len(channel.SubchannelRef), 341 | channel.Data.CallsStarted, 342 | channel.Data.CallsSucceeded, 343 | channel.Data.CallsFailed, 344 | elapsedTimestamp(now, channel.Data.LastCallStartedTimestamp), 345 | ) 346 | }) 347 | } 348 | 349 | func addrToString(addr *channelzpb.Address) string { 350 | if tcpaddr := addr.GetTcpipAddress(); tcpaddr != nil { 351 | return fmt.Sprintf("[%v]:%v", net.IP(tcpaddr.IpAddress).String(), tcpaddr.Port) 352 | } 353 | return "" 354 | } 355 | 356 | func (cc *ChannelzClient) ListServerSockets(ctx context.Context) { 357 | now := timeNow() 358 | 359 | cc.printf("%s\t%s\t%-40s\t%-20s\t%-20s\t%-20s\t%s\t%s\t%s\t%s\n", 360 | "ID", "ServerID", "Name", "RemoteName", "Local", "Remote", "Started", "Success", "Fail", "LastStream") 361 | 362 | cc.visitGetServers(ctx, func(server *channelzpb.Server) { 363 | cc.visitGetServerSockets(ctx, server.Ref.ServerId, func(socket *channelzpb.Socket) { 364 | localIP := addrToString(socket.Local) 365 | remoteIP := addrToString(socket.Remote) 366 | 367 | cc.printf("%d\t%-8d\t%-40s\t%-20s\t%-16s\t%-16s\t%-6d\t%-6d\t%-6d\t%-8s\n", 368 | socket.Ref.SocketId, 369 | server.Ref.ServerId, 370 | decorateEmpty(socket.Ref.Name), 371 | decorateEmpty(socket.RemoteName), 372 | decorateEmpty(localIP), 373 | decorateEmpty(remoteIP), 374 | socket.Data.StreamsStarted, 375 | socket.Data.StreamsSucceeded, 376 | socket.Data.StreamsFailed, 377 | elapsedTimestamp(now, socket.Data.LastRemoteStreamCreatedTimestamp), 378 | ) 379 | }) 380 | }) 381 | } 382 | 383 | func (cc *ChannelzClient) visitGetServerSockets(ctx context.Context, id int64, fn func(*channelzpb.Socket)) { 384 | lastSocketID := int64(0) 385 | for { 386 | res, err := cc.cc.GetServerSockets(ctx, &channelzpb.GetServerSocketsRequest{ 387 | ServerId: id, 388 | StartSocketId: lastSocketID, 389 | }) 390 | if err != nil { 391 | log.Fatalf("err: %v\n", err) 392 | } 393 | 394 | for _, ref := range res.SocketRef { 395 | socket, err := cc.cc.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: ref.SocketId}) 396 | if err != nil { 397 | log.Fatalf("err %v\n", err) 398 | } 399 | 400 | fn(socket.Socket) 401 | } 402 | if res.End { 403 | break 404 | } 405 | 406 | lastSocketID++ 407 | } 408 | } 409 | 410 | func (cc *ChannelzClient) TreeTopChannels(ctx context.Context) { 411 | now := timeNow() 412 | 413 | cc.visitTopChannels(ctx, func(channel *channelzpb.Channel) { 414 | cc.printf("%s (ID:%d) [%s]\n", 415 | channel.Data.Target, channel.Ref.ChannelId, 416 | channel.Data.State.State.String()) 417 | 418 | elapesed := elapsedTimestamp(now, channel.Data.LastCallStartedTimestamp) 419 | cc.printf(" [Calls] Started:%v, Succeeded:%v, Failed:%v, Last:%v\n", channel.Data.CallsStarted, channel.Data.CallsSucceeded, channel.Data.CallsFailed, elapesed) 420 | 421 | // for _, ev := range channel.Data.Trace.Events { 422 | // cc.printf("ev %v\n", ev) 423 | // } 424 | 425 | for _, socket := range channel.SocketRef { 426 | cc.printf("socket %v\n", socket) 427 | } 428 | 429 | for _, ch := range channel.ChannelRef { 430 | cc.printf("ch %v\n", ch) 431 | } 432 | 433 | if len(channel.SubchannelRef) != 0 { 434 | cc.printf(" [Subchannels]\n") 435 | } 436 | for _, ch := range channel.SubchannelRef { 437 | res, err := cc.cc.GetSubchannel(ctx, &channelzpb.GetSubchannelRequest{SubchannelId: ch.SubchannelId}) 438 | if err != nil { 439 | log.Fatalf("err %v", err) 440 | } 441 | 442 | subch := res.Subchannel 443 | cc.printf(" |-- %s (ID:%d) [%s]\n", 444 | subch.Data.Target, subch.Ref.SubchannelId, 445 | subch.Data.State.State.String()) 446 | 447 | elapesed := elapsedTimestamp(now, subch.Data.LastCallStartedTimestamp) 448 | cc.printf(" [Calls]: Started:%v, Succeeded:%v, Failed:%v, Last:%s\n", subch.Data.CallsStarted, subch.Data.CallsSucceeded, subch.Data.CallsFailed, elapesed) 449 | 450 | for _, socket := range subch.SocketRef { 451 | res, err := cc.cc.GetSocket(ctx, &channelzpb.GetSocketRequest{SocketId: socket.SocketId}) 452 | if err != nil { 453 | log.Fatalf("err %v\n", err) 454 | } 455 | 456 | socket := res.Socket 457 | cc.printf(" [Socket] ID:%v, Name:%v, RemoteName:%v", socket.Ref.SocketId, socket.Ref.Name, socket.RemoteName) 458 | cc.printf(", Local:") 459 | if addr := socket.Local.GetTcpipAddress(); addr != nil { 460 | cc.printf("[%v]:%v", net.IP(addr.IpAddress).String(), addr.Port) 461 | } 462 | cc.printf(" Remote:") 463 | if addr := socket.Remote.GetTcpipAddress(); addr != nil { 464 | cc.printf("[%v]:%v", net.IP(addr.IpAddress).String(), addr.Port) 465 | } 466 | cc.printf("\n") 467 | } 468 | 469 | for _, ch := range subch.ChannelRef { 470 | cc.printf("---- ch %v\n", ch) 471 | } 472 | for _, ch := range subch.SubchannelRef { 473 | cc.printf("---- ch %v\n", ch) 474 | } 475 | } 476 | 477 | cc.printf("\n") 478 | }) 479 | } 480 | 481 | func (cc *ChannelzClient) findTopChannel(ctx context.Context, name string) *channelzpb.Channel { 482 | n, err := strconv.Atoi(name) 483 | if err != nil { 484 | return cc.findTopChannelByName(ctx, name) 485 | } 486 | return cc.findTopChannelByID(ctx, int64(n)) 487 | } 488 | 489 | func (cc *ChannelzClient) findTopChannelByName(ctx context.Context, name string) *channelzpb.Channel { 490 | var found *channelzpb.Channel 491 | cc.visitTopChannels(ctx, func(channel *channelzpb.Channel) { 492 | if channel.Ref.Name == name { 493 | if found == nil { 494 | found = channel 495 | } 496 | } 497 | }) 498 | 499 | return found 500 | } 501 | 502 | func (cc *ChannelzClient) findTopChannelByID(ctx context.Context, id int64) *channelzpb.Channel { 503 | var found *channelzpb.Channel 504 | cc.visitTopChannels(ctx, func(channel *channelzpb.Channel) { 505 | if channel.Ref.ChannelId == id { 506 | found = channel 507 | } 508 | }) 509 | 510 | return found 511 | } 512 | 513 | func (cc *ChannelzClient) visitTopChannels(ctx context.Context, fn func(*channelzpb.Channel)) { 514 | lastChannelID := int64(0) 515 | retry := 0 516 | for { 517 | select { 518 | case <-ctx.Done(): 519 | return 520 | default: 521 | } 522 | 523 | ctx, cancel := context.WithTimeout(ctx, 5*time.Second*time.Duration(retry+1)) 524 | defer cancel() 525 | res, err := cc.cc.GetTopChannels(ctx, &channelzpb.GetTopChannelsRequest{StartChannelId: lastChannelID}) 526 | if err != nil { 527 | retry++ 528 | continue 529 | } 530 | 531 | for _, channel := range res.Channel { 532 | fn(channel) 533 | if id := channel.GetRef().GetChannelId(); id > lastChannelID { 534 | lastChannelID = id 535 | } 536 | } 537 | if res.End { 538 | break 539 | } 540 | 541 | lastChannelID++ 542 | } 543 | } 544 | --------------------------------------------------------------------------------