diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5e69f35 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +## [unreleased] + +### Added +* `--consumers` option of the `info` command now prints also information on + the connection. + +### Changed +* minor changes to output of `info` command (i.e. some values now are quoted) + + + diff --git a/README.md b/README.md index 495aacf..20be590 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Options: --api APIURI connect to given API server. If APIURI is omitted, the environment variable RABTAP_APIURI will be used. -b, --bindingkey KEY binding key to use in bind queue command. - --consumers include consumers in output of info command. + --consumers include consumers and connections in output of info command. -d, --durable create durable exchange/queue. -h, --help print this help. -j, --json print/save/publish message metadata and body to a diff --git a/cmd/main/broker_info.go b/cmd/main/broker_info.go index e307c78..e31c8d9 100644 --- a/cmd/main/broker_info.go +++ b/cmd/main/broker_info.go @@ -6,11 +6,13 @@ import "github.com/jandelgado/rabtap/pkg" // BrokerInfo collects information of an RabbitMQ broker type BrokerInfo struct { - Overview rabtap.RabbitOverview - Exchanges []rabtap.RabbitExchange - Queues []rabtap.RabbitQueue - Bindings []rabtap.RabbitBinding - Consumers []rabtap.RabbitConsumer + Overview rabtap.RabbitOverview + Exchanges []rabtap.RabbitExchange + Queues []rabtap.RabbitQueue + Bindings []rabtap.RabbitBinding + Connections []rabtap.RabbitConnection + Consumers []rabtap.RabbitConsumer + // Channels []rabtap.RabbitChannel // not yet used. } // NewBrokerInfo obtains infos on broker using the provided client object @@ -20,30 +22,40 @@ func NewBrokerInfo(client *rabtap.RabbitHTTPClient) (BrokerInfo, error) { var bi BrokerInfo // collect infos from rabtap.RabbitMQ API - bi.Overview, err = client.GetOverview() + bi.Overview, err = client.Overview() if err != nil { return bi, err } - bi.Exchanges, err = client.GetExchanges() + bi.Exchanges, err = client.Exchanges() if err != nil { return bi, err } - bi.Bindings, err = client.GetBindings() + bi.Bindings, err = client.Bindings() if err != nil { return bi, err } - bi.Queues, err = client.GetQueues() + bi.Queues, err = client.Queues() if err != nil { return bi, err } - bi.Consumers, err = client.GetConsumers() + bi.Connections, err = client.Connections() if err != nil { return bi, err } + bi.Consumers, err = client.Consumers() + if err != nil { + return bi, err + } + + // bi.Channels, err = client.Channels() + // if err != nil { + // return bi, err + // } + return bi, nil } diff --git a/cmd/main/broker_info_printer.go b/cmd/main/broker_info_printer.go index bafb1c1..2f1da4c 100644 --- a/cmd/main/broker_info_printer.go +++ b/cmd/main/broker_info_printer.go @@ -2,8 +2,6 @@ package main -// TODO factor out templates, make configurable. - import ( "bytes" "fmt" @@ -16,16 +14,62 @@ import ( "github.com/jandelgado/rabtap/pkg" ) +const ( + tplRootNode = ` + {{- printf "%s://%s%s" .URL.Scheme .URL.Host .URL.Path | URLColor }} + {{- if .Overview }} (broker ver='{{ .Overview.RabbitmqVersion }}', + {{- "" }} mgmt ver='{{ .Overview.ManagementVersion }}', + {{- "" }} cluster='{{ .Overview.ClusterName }}{{end}}')` + + tplConsumer = ` + {{- ConsumerColor .Consumer.ConsumerTag }} (consumer + {{- ""}} user='{{ .Consumer.ChannelDetails.User }}', chan=' + {{- .Consumer.ChannelDetails.Name }}')` + + tplConnection = ` + {{- ""}}'{{ ConnectionColor .Connection.Name }}' (connection + {{- ""}} client='{{ .Connection.ClientProperties.Product}}', + {{- ""}} host='{{ .Connection.Host }}:{{ .Connection.Port }}', + {{- ""}} peer='{{ .Connection.PeerHost }}:{{ .Connection.PeerPort }}')` + + tplExchange = ` + {{- ExchangeColor .PrintName }} (exchange, type '{{ .Exchange.Type }}' + {{- if and .Config.ShowStats .Exchange.MessageStats }}, in=( + {{- .Exchange.MessageStats.PublishIn }}, {{printf "%.1f" .Exchange.MessageStats.PublishInDetails.Rate}}/s) msg, out=( + {{- .Exchange.MessageStats.PublishOut }}, {{printf "%.1f" .Exchange.MessageStats.PublishOutDetails.Rate}}/s) msg + {{- end }}, {{ .ExchangeFlags }})` + + tplQueue = ` + {{- QueueColor .Binding.Destination }} (queue, + {{- with .Binding.RoutingKey }} key='{{ KeyColor .}}',{{end}} + {{- with .Binding.Arguments}} args='{{ KeyColor .}}',{{end}} + {{- if .Config.ShowStats }} + {{- .Queue.Consumers }} cons, ( + {{- .Queue.Messages }}, {{printf "%.1f" .Queue.MessagesDetails.Rate}}/s) msg, ( + {{- .Queue.MessagesReady }}, {{printf "%.1f" .Queue.MessagesReadyDetails.Rate}}/s) msg ready, + {{- end }} + {{- if .Queue.IdleSince}}{{- " idle since "}}{{ .Queue.IdleSince}}{{else}}{{ " running" }}{{end}} + {{- ""}}, {{ .QueueFlags}})` +) + +// BrokerInfoPrinterConfig controls bevaviour auf PrintBrokerInfo +type BrokerInfoPrinterConfig struct { + ShowDefaultExchange bool + ShowConsumers bool + ShowStats bool + NoColor bool +} + // BrokerInfoPrinter prints nicely treeish infos desribing a brokers // topology type BrokerInfoPrinter struct { //brokerInfo BrokerInfo - config PrintBrokerInfoConfig + config BrokerInfoPrinterConfig colorizer ColorPrinter } // NewBrokerInfoPrinter constructs a new object to print a broker info -func NewBrokerInfoPrinter(config PrintBrokerInfoConfig) *BrokerInfoPrinter { +func NewBrokerInfoPrinter(config BrokerInfoPrinterConfig) *BrokerInfoPrinter { s := BrokerInfoPrinter{ config: config, colorizer: NewColorPrinter(config.NoColor), @@ -33,14 +77,6 @@ func NewBrokerInfoPrinter(config PrintBrokerInfoConfig) *BrokerInfoPrinter { return &s } -// PrintBrokerInfoConfig controls bevaviour auf PrintBrokerInfo -type PrintBrokerInfoConfig struct { - ShowDefaultExchange bool - ShowConsumers bool - ShowStats bool - NoColor bool -} - // findQueueByName searches in the queues array for a queue with the given // name and vhost. RabbitQueue element is returned on succes, otherwise nil. func findQueueByName(queues []rabtap.RabbitQueue, @@ -63,6 +99,59 @@ func findExchangeByName(exchanges []rabtap.RabbitExchange, return nil } +// currently not used. +// func findChannelByName(channels []rabtap.RabbitChannel, +// vhost, channelName string) *rabtap.RabbitChannel { +// for _, channel := range channels { +// if channel.Name == channelName && channel.Vhost == vhost { +// return &channel +// } +// } +// return nil +// } + +func findConnectionByName(conns []rabtap.RabbitConnection, + vhost, connName string) *rabtap.RabbitConnection { + for _, conn := range conns { + if conn.Name == connName && conn.Vhost == vhost { + return &conn + } + } + return nil +} + +func findConsumerByQueue(consumers []rabtap.RabbitConsumer, + vhost, queueName string) *rabtap.RabbitConsumer { + for _, consumer := range consumers { + if consumer.Queue.Vhost == vhost && + consumer.Queue.Name == queueName { + return &consumer + } + } + return nil +} + +func getBindingsForExchange(exchange *rabtap.RabbitExchange, bindings []rabtap.RabbitBinding) []rabtap.RabbitBinding { + var result []rabtap.RabbitBinding + for _, binding := range bindings { + if binding.Source == exchange.Name && + binding.Vhost == exchange.Vhost { + result = append(result, binding) + } + } + return result +} + +func filterStringList(flags []bool, list []string) []string { + result := []string{} + for i, s := range list { + if flags[i] { + result = append(result, s) + } + } + return result +} + // resolveTemplate resolves a template for use in the broker info printer, // with support for colored output. name is just an informational name // passed to the template ctor. tpl is the actual template and args @@ -79,159 +168,132 @@ func (s BrokerInfoPrinter) resolveTemplate(name string, tpl string, args interfa return buf.String() } -func (s BrokerInfoPrinter) renderQueueFlagsAsString(queue rabtap.RabbitQueue) string { - var flags []string - if queue.Durable { - flags = append(flags, "D") - } - if queue.AutoDelete { - flags = append(flags, "AD") - } - if queue.Exclusive { - flags = append(flags, "EX") - } - return "[" + strings.Join(flags, "|") + "]" +func (s BrokerInfoPrinter) renderQueueFlagsAsString(queue *rabtap.RabbitQueue) string { + flags := []bool{queue.Durable, queue.AutoDelete, queue.Exclusive} + names := []string{"D", "AD", "EX"} + return "[" + strings.Join(filterStringList(flags, names), "|") + "]" } -func (s BrokerInfoPrinter) renderExchangeFlagsAsString(exchange rabtap.RabbitExchange) string { - var flags []string - if exchange.Durable { - flags = append(flags, "D") - } - if exchange.AutoDelete { - flags = append(flags, "AD") - } - if exchange.Internal { - flags = append(flags, "I") +func (s BrokerInfoPrinter) renderExchangeFlagsAsString(exchange *rabtap.RabbitExchange) string { + flags := []bool{exchange.Durable, exchange.AutoDelete, exchange.Internal} + names := []string{"D", "AD", "I"} + return "[" + strings.Join(filterStringList(flags, names), "|") + "]" +} + +func (s BrokerInfoPrinter) renderConsumerElementAsString(consumer *rabtap.RabbitConsumer) string { + type ConsumerInfo struct { + Config BrokerInfoPrinterConfig + Consumer *rabtap.RabbitConsumer } - return "[" + strings.Join(flags, "|") + "]" + args := ConsumerInfo{s.config, consumer} + return s.resolveTemplate("consumer-tpl", tplConsumer, args) } -func (s BrokerInfoPrinter) renderConsumerElementAsString(consumer rabtap.RabbitConsumer) string { - return fmt.Sprintf("%s (consumer, %s)", - s.colorizer.Consumer(consumer.ConsumerTag), - consumer.ChannelDetails.Name) +func (s BrokerInfoPrinter) renderConnectionElementAsString(conn *rabtap.RabbitConnection) string { + type ConnectionInfo struct { + Config BrokerInfoPrinterConfig + Connection *rabtap.RabbitConnection + } + args := ConnectionInfo{s.config, conn} + return s.resolveTemplate("connnection-tpl", tplConnection, args) } -func (s BrokerInfoPrinter) renderQueueElementAsString(queue rabtap.RabbitQueue, binding rabtap.RabbitBinding) string { +func (s BrokerInfoPrinter) renderQueueElementAsString(queue *rabtap.RabbitQueue, binding *rabtap.RabbitBinding) string { queueFlags := s.renderQueueFlagsAsString(queue) type QueueInfo struct { - Config PrintBrokerInfoConfig - Binding rabtap.RabbitBinding - Queue rabtap.RabbitQueue + Config BrokerInfoPrinterConfig + Binding *rabtap.RabbitBinding + Queue *rabtap.RabbitQueue QueueFlags string } args := QueueInfo{s.config, binding, queue, queueFlags} - - const tpl = ` - {{- QueueColor .Binding.Destination }} (queue, - {{- with .Binding.RoutingKey }} key={{ KeyColor .}},{{end}} - {{- with .Binding.Arguments}} args={{ KeyColor .}},{{end}} - {{- if .Config.ShowStats }} - {{- .Queue.Consumers }} cons, ( - {{- .Queue.Messages }}, {{printf "%.1f" .Queue.MessagesDetails.Rate}}/s) msg, ( - {{- .Queue.MessagesReady }}, {{printf "%.1f" .Queue.MessagesReadyDetails.Rate}}/s) msg ready, - {{- end }} - {{- if .Queue.IdleSince}}{{- " idle since "}}{{ .Queue.IdleSince}}{{else}}{{ " running" }}{{end}} - {{- ""}}, {{ .QueueFlags}})` - - return s.resolveTemplate("queue-tpl", tpl, args) + return s.resolveTemplate("queue-tpl", tplQueue, args) } func (s BrokerInfoPrinter) renderRootNodeAsString(rabbitURL url.URL, overview rabtap.RabbitOverview) string { type RootInfo struct { - Config PrintBrokerInfoConfig + Config BrokerInfoPrinterConfig URL url.URL Overview rabtap.RabbitOverview } args := RootInfo{s.config, rabbitURL, overview} - const tpl = `{{ printf "%s://%s%s" .URL.Scheme .URL.Host .URL.Path | URLColor }} - {{- if .Overview }} (broker ver={{ .Overview.RabbitmqVersion }}, - {{- "" }} mgmt ver={{ .Overview.ManagementVersion }}, - {{- "" }} cluster={{ .Overview.ClusterName }}{{end}})` - - return s.resolveTemplate("rootnode", tpl, args) + return s.resolveTemplate("rootnode", tplRootNode, args) } -func (s BrokerInfoPrinter) renderExchangeElementAsString(exchange rabtap.RabbitExchange) string { +func (s BrokerInfoPrinter) renderExchangeElementAsString(exchange *rabtap.RabbitExchange) string { printName := exchange.Name if printName == "" { printName = "(default)" } type ExchangeInfo struct { - Config PrintBrokerInfoConfig - Exchange rabtap.RabbitExchange + Config BrokerInfoPrinterConfig + Exchange *rabtap.RabbitExchange ExchangeFlags string PrintName string } exchangeFlags := s.renderExchangeFlagsAsString(exchange) args := ExchangeInfo{s.config, exchange, exchangeFlags, printName} - - const tpl = `{{ ExchangeColor .PrintName }} (exchange, type '{{ .Exchange.Type }}' - {{- if and .Config.ShowStats .Exchange.MessageStats }}, in=( - {{- .Exchange.MessageStats.PublishIn }}, {{printf "%.1f" .Exchange.MessageStats.PublishInDetails.Rate}}/s) msg, out=( - {{- .Exchange.MessageStats.PublishOut }}, {{printf "%.1f" .Exchange.MessageStats.PublishOutDetails.Rate}}/s) msg - {{- end }}, {{ .ExchangeFlags }})` - - return s.resolveTemplate("exchange-tpl", tpl, args) + return s.resolveTemplate("exchange-tpl", tplExchange, args) } -func (s BrokerInfoPrinter) addConsumerNode(node *TreeNode, consumers []rabtap.RabbitConsumer, - exchange *rabtap.RabbitExchange, queue *rabtap.RabbitQueue) { +// TODO remove exchange, use queue.Vhost +func (s BrokerInfoPrinter) addConsumerNodes(node *TreeNode, + queue *rabtap.RabbitQueue, brokerInfo BrokerInfo) { - for _, consumer := range consumers { - if consumer.Queue.Vhost == exchange.Vhost && + vhost := queue.Vhost + for _, consumer := range brokerInfo.Consumers { + if consumer.Queue.Vhost == vhost && consumer.Queue.Name == queue.Name { - node.AddChild(s.renderConsumerElementAsString(consumer)) + consumerNode := node.AddChild(s.renderConsumerElementAsString(&consumer)) + s.addConnectionNode(consumerNode, vhost, consumer.ChannelDetails.ConnectionName, brokerInfo) } } } -func getBindingsForExchange(exchange *rabtap.RabbitExchange, bindings []rabtap.RabbitBinding) []rabtap.RabbitBinding { - var result []rabtap.RabbitBinding - for _, binding := range bindings { - if binding.Source == exchange.Name && - binding.Vhost == exchange.Vhost { - result = append(result, binding) - } +func (s BrokerInfoPrinter) addConnectionNode(node *TreeNode, + vhost string, connName string, brokerInfo BrokerInfo) { + connInfo := findConnectionByName(brokerInfo.Connections, vhost, connName) + if connInfo != nil { + node.AddChild(s.renderConnectionElementAsString(connInfo)) } - return result +} + +func (s BrokerInfoPrinter) addQueue(node *TreeNode, binding *rabtap.RabbitBinding, exchange *rabtap.RabbitExchange, brokerInfo BrokerInfo) *TreeNode { + // standard binding of queue to exchange + queue := findQueueByName(brokerInfo.Queues, + binding.Vhost, + binding.Destination) + if queue == nil { + // we test for nil because (at least in theory) a queue can disappear + // since we are making various non-transactional API calls + queue = &rabtap.RabbitQueue{Name: binding.Destination} + } + queueText := s.renderQueueElementAsString(queue, binding) + queueNode := node.AddChild(queueText) + + if s.config.ShowConsumers { + s.addConsumerNodes(queueNode, queue, brokerInfo) + } + return queueNode } // addExchange recursively (in case of exchange-exchange binding) an exchange to the // given node. -// TODO simplify func (s BrokerInfoPrinter) addExchange(node *TreeNode, exchange *rabtap.RabbitExchange, brokerInfo BrokerInfo) { - exchangeNodeText := s.renderExchangeElementAsString(*exchange) + exchangeNodeText := s.renderExchangeElementAsString(exchange) exchangeNode := node.AddChild(exchangeNodeText) // process all bindings for current exchange for _, binding := range getBindingsForExchange(exchange, brokerInfo.Bindings) { switch binding.DestinationType { case "queue": - // standard binding of queue to exchange - queue := findQueueByName(brokerInfo.Queues, - binding.Vhost, - binding.Destination) - if queue == nil { - // we test for nil because (at least in theory) a queue - // can disappear since we are making various non-transactional - // API calls - queue = &rabtap.RabbitQueue{Name: binding.Destination} - } - queueText := s.renderQueueElementAsString(*queue, binding) - queueNode := exchangeNode.AddChild(queueText) - - // add consumers of queue - if s.config.ShowConsumers { - s.addConsumerNode(queueNode, brokerInfo.Consumers, exchange, queue) - } + s.addQueue(exchangeNode, &binding, exchange, brokerInfo) case "exchange": s.addExchange(exchangeNode, findExchangeByName( // TODO can be nil @@ -261,12 +323,14 @@ func (s BrokerInfoPrinter) getExchangesToDisplay( return result } -// Print render given brokerInfo into a tree-view: +// Print renders given brokerInfo into a tree-view: // RabbitMQ-Host // +--VHost // +--Exchange // +--Queue bound to exchange -// +--Consumer +// +--Consumer (optional) +// +--Connection +// func (s BrokerInfoPrinter) Print(brokerInfo BrokerInfo, rootNode string, out io.Writer) error { diff --git a/cmd/main/broker_info_printer_test.go b/cmd/main/broker_info_printer_test.go index 4e87ba1..b5adb16 100644 --- a/cmd/main/broker_info_printer_test.go +++ b/cmd/main/broker_info_printer_test.go @@ -21,7 +21,7 @@ func TestResolveTemplate(t *testing.T) { const tpl = "hello {{ .Name }}" brokerInfoPrinter := NewBrokerInfoPrinter( - PrintBrokerInfoConfig{ + BrokerInfoPrinterConfig{ ShowStats: false, ShowConsumers: false, ShowDefaultExchange: false, @@ -48,6 +48,7 @@ func TestFindExchangeByNameNotFound(t *testing.T) { exchange := findExchangeByName(exchanges, "/", "not-available") assert.Nil(t, exchange) } + func TestFindQueueByName(t *testing.T) { queues := []rabtap.RabbitQueue{ {Name: "q1", Vhost: "vhost"}, @@ -67,15 +68,38 @@ func TestFindQueueByNameNotFound(t *testing.T) { assert.Nil(t, queue) } -/* -func TestFormatConsumerElement(t *testing.T) { +func TestFilterStringListOfEmptyLists(t *testing.T) { + flags := []bool{} + strs := []string{} + assert.Equal(t, []string{}, filterStringList(flags, strs)) +} + +func TestFilterStringListOneElementKeptInList(t *testing.T) { + flags := []bool{false, true, false} + strs := []string{"A", "B", "C"} + assert.Equal(t, []string{"B"}, filterStringList(flags, strs)) +} + +func TestFindConnectionByName(t *testing.T) { + conns := []rabtap.RabbitConnection{ + {Name: "c1", Vhost: "vhost"}, + {Name: "c2", Vhost: "vhost"}, + } + conn := findConnectionByName(conns, "vhost", "c2") + assert.Equal(t, "c2", conn.Name) + assert.Equal(t, "vhost", conn.Vhost) +} - consumer := rabtap.RabbitConsumer{ConsumerTag: "consumertag"} - consumer.ChannelDetails.Name = "details" - assert.Equal(t, "consumertag (consumer, details)", - formatConsumerElement(consumer, noColorFunc)) +func TestFindConsumerByName(t *testing.T) { + con := rabtap.RabbitConsumer{} + con.Queue.Name = "q1" + con.Queue.Vhost = "vhost" + cons := []rabtap.RabbitConsumer{con} + foundCon := findConsumerByQueue(cons, "vhost", "q1") + assert.Equal(t, "q1", foundCon.Queue.Name) + assert.Equal(t, "vhost", foundCon.Queue.Vhost) } -**/ + func ExampleBrokerInfoPrinter_Print() { mock := testcommon.NewRabbitAPIMock(testcommon.MockModeStd) @@ -83,7 +107,7 @@ func ExampleBrokerInfoPrinter_Print() { client := rabtap.NewRabbitHTTPClient(mock.URL, &tls.Config{}) brokerInfoPrinter := NewBrokerInfoPrinter( - PrintBrokerInfoConfig{ + BrokerInfoPrinterConfig{ ShowStats: false, ShowConsumers: true, ShowDefaultExchange: false, @@ -98,7 +122,7 @@ func ExampleBrokerInfoPrinter_Print() { } // Output: - // http://rabbitmq/api (broker ver=3.6.9, mgmt ver=3.6.9, cluster=rabbit@08f57d1fe8ab) + // http://rabbitmq/api (broker ver='3.6.9', mgmt ver='3.6.9', cluster='rabbit@08f57d1fe8ab') // └── Vhost / // ├── amq.direct (exchange, type 'direct', [D]) // ├── amq.fanout (exchange, type 'fanout', [D]) @@ -108,18 +132,19 @@ func ExampleBrokerInfoPrinter_Print() { // ├── amq.rabbitmq.trace (exchange, type 'topic', [D|I]) // ├── amq.topic (exchange, type 'topic', [D]) // ├── test-direct (exchange, type 'direct', [D|AD|I]) - // │ ├── direct-q1 (queue, key=direct-q1, running, [D]) - // │ │ ├── some_consumer (consumer, 172.17.0.1:58938 -> 172.17.0.2:5672 (2)) - // │ │ └── another_consumer w/ faulty channel (consumer, ) - // │ └── direct-q2 (queue, key=direct-q2, running, [D]) + // │ ├── direct-q1 (queue, key='direct-q1', running, [D]) + // │ │ ├── some_consumer (consumer user='guest', chan='172.17.0.1:40874 -> 172.17.0.2:5672 (1)') + // │ │ │ └── '172.17.0.1:40874 -> 172.17.0.2:5672' (connection client='https://github.com/streadway/amqp', host='172.17.0.2:5672', peer='172.17.0.1:40874') + // │ │ └── another_consumer w/ faulty channel (consumer user='', chan='') + // │ └── direct-q2 (queue, key='direct-q2', running, [D]) // ├── test-fanout (exchange, type 'fanout', [D]) // │ ├── fanout-q1 (queue, idle since 2017-05-25 19:14:32, [D]) // │ └── fanout-q2 (queue, idle since 2017-05-25 19:14:32, [D]) // ├── test-headers (exchange, type 'headers', [D|AD]) - // │ ├── header-q1 (queue, key=headers-q1, idle since 2017-05-25 19:14:53, [D]) - // │ └── header-q2 (queue, key=headers-q2, idle since 2017-05-25 19:14:47, [D]) + // │ ├── header-q1 (queue, key='headers-q1', idle since 2017-05-25 19:14:53, [D]) + // │ └── header-q2 (queue, key='headers-q2', idle since 2017-05-25 19:14:47, [D]) // └── test-topic (exchange, type 'topic', [D]) - // ├── topic-q1 (queue, key=topic-q1, idle since 2017-05-25 19:14:17, [D|AD|EX]) - // └── topic-q2 (queue, key=topic-q2, idle since 2017-05-25 19:14:21, [D]) + // ├── topic-q1 (queue, key='topic-q1', idle since 2017-05-25 19:14:17, [D|AD|EX]) + // └── topic-q2 (queue, key='topic-q2', idle since 2017-05-25 19:14:21, [D]) } diff --git a/cmd/main/cmd_info.go b/cmd/main/cmd_info.go index 338f720..ecb2ac0 100644 --- a/cmd/main/cmd_info.go +++ b/cmd/main/cmd_info.go @@ -11,10 +11,10 @@ import ( // CmdInfoArg contains arguments for the info command type CmdInfoArg struct { - rootNode string - client *rabtap.RabbitHTTPClient - printBrokerInfoConfig PrintBrokerInfoConfig - out io.Writer + rootNode string + client *rabtap.RabbitHTTPClient + printConfig BrokerInfoPrinterConfig + out io.Writer } // cmdInfo queries the rabbitMQ brokers REST api and dispays infos @@ -22,6 +22,6 @@ type CmdInfoArg struct { func cmdInfo(cmd CmdInfoArg) { brokerInfo, err := NewBrokerInfo(cmd.client) failOnError(err, "failed retrieving info from rabbitmq REST api", os.Exit) - brokerInfoPrinter := NewBrokerInfoPrinter(cmd.printBrokerInfoConfig) + brokerInfoPrinter := NewBrokerInfoPrinter(cmd.printConfig) brokerInfoPrinter.Print(brokerInfo, cmd.rootNode, cmd.out) } diff --git a/cmd/main/cmd_info_integration_test.go b/cmd/main/cmd_info_integration_test.go index 73c14c5..6c5cd01 100644 --- a/cmd/main/cmd_info_integration_test.go +++ b/cmd/main/cmd_info_integration_test.go @@ -18,7 +18,7 @@ func TestCmdInfoRootNodeOnly(t *testing.T) { apiMock := testcommon.NewRabbitAPIMock(testcommon.MockModeEmpty) client := rabtap.NewRabbitHTTPClient(apiMock.URL, &tls.Config{}) - printBrokerInfoConfig := PrintBrokerInfoConfig{ + printConfig := BrokerInfoPrinterConfig{ ShowStats: false, ShowConsumers: false, ShowDefaultExchange: false, @@ -26,10 +26,10 @@ func TestCmdInfoRootNodeOnly(t *testing.T) { buf := bytes.NewBufferString("") cmdInfo(CmdInfoArg{ - rootNode: "http://x:y@rootnode", - client: client, - printBrokerInfoConfig: printBrokerInfoConfig, - out: buf}) - assert.Equal(t, "http://rootnode (broker ver=3.6.9, mgmt ver=3.6.9, cluster=rabbit@08f57d1fe8ab)", + rootNode: "http://x:y@rootnode", + client: client, + printConfig: printConfig, + out: buf}) + assert.Equal(t, "http://rootnode (broker ver='3.6.9', mgmt ver='3.6.9', cluster='rabbit@08f57d1fe8ab')", strings.TrimSpace(buf.String())) } diff --git a/cmd/main/color_printer.go b/cmd/main/color_printer.go index bfda1fb..01038c6 100644 --- a/cmd/main/color_printer.go +++ b/cmd/main/color_printer.go @@ -13,13 +13,15 @@ import ( ) const ( - colorURL = color.FgHiWhite - colorVHost = color.FgMagenta - colorExchange = color.FgHiBlue - colorQueue = color.FgHiYellow - colorConsumer = color.FgHiGreen - colorMessage = color.FgHiYellow - colorKey = color.FgHiCyan + colorURL = color.FgHiWhite + colorVHost = color.FgMagenta + colorExchange = color.FgHiBlue + colorQueue = color.FgHiYellow + colorConnection = color.FgRed + colorChannel = color.FgWhite + colorConsumer = color.FgHiGreen + colorMessage = color.FgHiYellow + colorKey = color.FgHiCyan ) // ColorPrinterFunc takes fmt.Sprint like arguments and add colors @@ -27,25 +29,29 @@ type ColorPrinterFunc func(a ...interface{}) string // ColorPrinter allows to print various items colorized type ColorPrinter struct { - URL ColorPrinterFunc - VHost ColorPrinterFunc - Exchange ColorPrinterFunc - Queue ColorPrinterFunc - Consumer ColorPrinterFunc - Message ColorPrinterFunc - Key ColorPrinterFunc + URL ColorPrinterFunc + VHost ColorPrinterFunc + Exchange ColorPrinterFunc + Queue ColorPrinterFunc + Connection ColorPrinterFunc + Channel ColorPrinterFunc + Consumer ColorPrinterFunc + Message ColorPrinterFunc + Key ColorPrinterFunc } // GetFuncMap returns a function map that can be used in a template. func (s ColorPrinter) GetFuncMap() map[string]interface{} { return map[string]interface{}{ - "QueueColor": s.Queue, - "ExchangeColor": s.Exchange, - "URLColor": s.URL, - "VHostColor": s.VHost, - "ConsumerColor": s.Consumer, - "MessageColor": s.Message, - "KeyColor": s.Key} + "QueueColor": s.Queue, + "ConnectionColor": s.Connection, + "ChannelColor": s.Channel, + "ExchangeColor": s.Exchange, + "URLColor": s.URL, + "VHostColor": s.VHost, + "ConsumerColor": s.Consumer, + "MessageColor": s.Message, + "KeyColor": s.Key} } // NewColorableWriter returns a colorable writer for the given file (e.g @@ -61,16 +67,25 @@ func NewColorPrinter(noColor bool) ColorPrinter { nullPrinter := func(a ...interface{}) string { return fmt.Sprint(a...) } - return ColorPrinter{nullPrinter, nullPrinter, - nullPrinter, nullPrinter, nullPrinter, nullPrinter, nullPrinter} + return ColorPrinter{nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter, + nullPrinter} } return ColorPrinter{ - URL: color.New(colorURL).SprintFunc(), - VHost: color.New(colorVHost).SprintFunc(), - Exchange: color.New(colorExchange).SprintFunc(), - Queue: color.New(colorQueue).SprintFunc(), - Consumer: color.New(colorConsumer).SprintFunc(), - Message: color.New(colorMessage).SprintFunc(), - Key: color.New(colorKey).SprintFunc(), + URL: color.New(colorURL).SprintFunc(), + VHost: color.New(colorVHost).SprintFunc(), + Exchange: color.New(colorExchange).SprintFunc(), + Queue: color.New(colorQueue).SprintFunc(), + Channel: color.New(colorChannel).SprintFunc(), + Connection: color.New(colorConnection).SprintFunc(), + Consumer: color.New(colorConsumer).SprintFunc(), + Message: color.New(colorMessage).SprintFunc(), + Key: color.New(colorKey).SprintFunc(), } } diff --git a/cmd/main/command_line.go b/cmd/main/command_line.go index 04682ee..996dd2f 100644 --- a/cmd/main/command_line.go +++ b/cmd/main/command_line.go @@ -54,7 +54,7 @@ Options: --api APIURI connect to given API server. If APIURI is omitted, the environment variable RABTAP_APIURI will be used. -b, --bindingkey KEY binding key to use in bind queue command. - --consumers include consumers in output of info command. + --consumers include consumers and connections in output of info command. -d, --durable create durable exchange/queue. -h, --help print this help. -j, --json print/save/publish message metadata and body to a diff --git a/cmd/main/main.go b/cmd/main/main.go index ad8ab1b..acfbc53 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -40,7 +40,7 @@ func startCmdInfo(args CommandLineArgs, title string) { cmdInfo(CmdInfoArg{ rootNode: title, client: rabtap.NewRabbitHTTPClient(args.APIURI, getTLSConfig(args.InsecureTLS)), - printBrokerInfoConfig: PrintBrokerInfoConfig{ + printConfig: BrokerInfoPrinterConfig{ ShowStats: args.ShowStats, ShowConsumers: args.ShowConsumers, ShowDefaultExchange: args.ShowDefaultExchange, diff --git a/pkg/discovery.go b/pkg/discovery.go index cd64987..7557cf2 100644 --- a/pkg/discovery.go +++ b/pkg/discovery.go @@ -14,7 +14,7 @@ import ( func DiscoverBindingsForExchange(rabbitAPIClient *RabbitHTTPClient, vhost, exchangeName string) ([]string, error) { var bindingKeys []string - exchanges, err := rabbitAPIClient.GetExchanges() + exchanges, err := rabbitAPIClient.Exchanges() if err != nil { return nil, err @@ -37,7 +37,7 @@ func DiscoverBindingsForExchange(rabbitAPIClient *RabbitHTTPClient, vhost, excha switch *exchangeType { case amqp.ExchangeDirect: // filter out all bindings for given exchange - bindings, err := rabbitAPIClient.GetBindings() + bindings, err := rabbitAPIClient.Bindings() if err != nil { return nil, err diff --git a/pkg/exchange_integration_test.go b/pkg/exchange_integration_test.go index ed4560e..4da9b36 100644 --- a/pkg/exchange_integration_test.go +++ b/pkg/exchange_integration_test.go @@ -37,7 +37,7 @@ func TestIntegrationAmqpExchangeCreateRemove(t *testing.T) { &tls.Config{}) // make sure exchange does not exist before creation - exchanges, err := client.GetExchanges() + exchanges, err := client.Exchanges() assert.Nil(t, err) assert.Equal(t, -1, findExchange(testName, exchanges)) @@ -48,7 +48,7 @@ func TestIntegrationAmqpExchangeCreateRemove(t *testing.T) { assert.Nil(t, err) // check if exchange was created - exchanges, err = client.GetExchanges() + exchanges, err = client.Exchanges() assert.Nil(t, err) assert.NotEqual(t, -1, findExchange(testName, exchanges)) @@ -57,7 +57,7 @@ func TestIntegrationAmqpExchangeCreateRemove(t *testing.T) { assert.Nil(t, err) // check if exchange was deleted - exchanges, err = client.GetExchanges() + exchanges, err = client.Exchanges() assert.Nil(t, err) assert.Equal(t, -1, findExchange(testName, exchanges)) } diff --git a/pkg/queue_integration_test.go b/pkg/queue_integration_test.go index 91e28b3..c422f89 100644 --- a/pkg/queue_integration_test.go +++ b/pkg/queue_integration_test.go @@ -50,7 +50,7 @@ func TestIntegrationAmqpQueueCreateBindRemove(t *testing.T) { &tls.Config{}) // make sure queue does not exist before creation - queues, err := client.GetQueues() + queues, err := client.Queues() assert.Nil(t, err) assert.Equal(t, -1, findQueue(queueTestName, queues)) @@ -61,22 +61,21 @@ func TestIntegrationAmqpQueueCreateBindRemove(t *testing.T) { assert.Nil(t, err) // check if queue was created - queues, err = client.GetQueues() + queues, err = client.Queues() assert.Nil(t, err) assert.NotEqual(t, -1, findQueue(queueTestName, queues)) // bind queue to exchange err = BindQueueToExchange(ch, queueTestName, keyTestName, exchangeTestName) assert.Nil(t, err) - bindings, err := client.GetBindings() + bindings, err := client.Bindings() assert.Nil(t, err) assert.NotEqual(t, -1, findBinding(queueTestName, exchangeTestName, keyTestName, bindings)) // finally remove queue err = RemoveQueue(ch, queueTestName, false, false) assert.Nil(t, err) - queues, err = client.GetQueues() + queues, err = client.Queues() assert.Nil(t, err) assert.Equal(t, -1, findQueue(queueTestName, queues)) - } diff --git a/pkg/rabbitmq_rest_client.go b/pkg/rabbitmq_rest_client.go index 0fee8c3..b37e332 100644 --- a/pkg/rabbitmq_rest_client.go +++ b/pkg/rabbitmq_rest_client.go @@ -13,7 +13,7 @@ import ( // RabbitHTTPClient is a minimal client to the rabbitmq management REST api. // It implements only functions needed by this tool (i.e. GET on some of the -// resources). The messages strucdt were generated using json-to-go ( +// resources). The messages structs were generated using json-to-go ( // https://mholt.github.io/json-to-go/ RabbitMQ HTTP API documentation can be). type RabbitHTTPClient struct { uri string @@ -26,6 +26,114 @@ func NewRabbitHTTPClient(uri string, return &RabbitHTTPClient{uri, tlsConfig} } +// RabbitConnection models the /connections resource of the rabbitmq http api +type RabbitConnection struct { + ReductionsDetails struct { + Rate float64 `json:"rate"` + } `json:"reductions_details"` + Reductions int `json:"reductions"` + RecvOctDetails struct { + Rate float64 `json:"rate"` + } `json:"recv_oct_details"` + RecvOct int `json:"recv_oct"` + SendOctDetails struct { + Rate float64 `json:"rate"` + } `json:"send_oct_details"` + SendOct int `json:"send_oct"` + ConnectedAt int64 `json:"connected_at"` + ClientProperties struct { + Product string `json:"product"` + Version string `json:"version"` + Capabilities struct { + ConnectionBlocked bool `json:"connection.blocked"` + ConsumerCancelNotify bool `json:"consumer_cancel_notify"` + } `json:"capabilities"` + } `json:"client_properties"` + ChannelMax int `json:"channel_max"` + FrameMax int `json:"frame_max"` + Timeout int `json:"timeout"` + Vhost string `json:"vhost"` + User string `json:"user"` + Protocol string `json:"protocol"` + SslHash interface{} `json:"ssl_hash"` + SslCipher interface{} `json:"ssl_cipher"` + SslKeyExchange interface{} `json:"ssl_key_exchange"` + SslProtocol interface{} `json:"ssl_protocol"` + AuthMechanism string `json:"auth_mechanism"` + PeerCertValidity interface{} `json:"peer_cert_validity"` + PeerCertIssuer interface{} `json:"peer_cert_issuer"` + PeerCertSubject interface{} `json:"peer_cert_subject"` + Ssl bool `json:"ssl"` + PeerHost string `json:"peer_host"` + Host string `json:"host"` + PeerPort int `json:"peer_port"` + Port int `json:"port"` + Name string `json:"name"` + Node string `json:"node"` + Type string `json:"type"` + GarbageCollection struct { + MinorGcs int `json:"minor_gcs"` + FullsweepAfter int `json:"fullsweep_after"` + MinHeapSize int `json:"min_heap_size"` + MinBinVheapSize int `json:"min_bin_vheap_size"` + MaxHeapSize int `json:"max_heap_size"` + } `json:"garbage_collection"` + Channels int `json:"channels"` + State string `json:"state"` + SendPend int `json:"send_pend"` + SendCnt int `json:"send_cnt"` + RecvCnt int `json:"recv_cnt"` +} + +// RabbitChannel models the /channels resource of the rabbitmq http api +type RabbitChannel struct { + ReductionsDetails struct { + Rate float64 `json:"rate"` + } `json:"reductions_details"` + Reductions int `json:"reductions"` + MessageStats struct { + ReturnUnroutableDetails struct { + Rate float64 `json:"rate"` + } `json:"return_unroutable_details"` + ReturnUnroutable int `json:"return_unroutable"` + ConfirmDetails struct { + Rate float64 `json:"rate"` + } `json:"confirm_details"` + Confirm int `json:"confirm"` + PublishDetails struct { + Rate float64 `json:"rate"` + } `json:"publish_details"` + Publish int `json:"publish"` + } `json:"message_stats"` + Vhost string `json:"vhost"` + User string `json:"user"` + Number int `json:"number"` + Name string `json:"name"` + Node string `json:"node"` + ConnectionDetails struct { + PeerHost string `json:"peer_host"` + PeerPort int `json:"peer_port"` + Name string `json:"name"` + } `json:"connection_details"` + GarbageCollection struct { + MinorGcs int `json:"minor_gcs"` + FullsweepAfter int `json:"fullsweep_after"` + MinHeapSize int `json:"min_heap_size"` + MinBinVheapSize int `json:"min_bin_vheap_size"` + MaxHeapSize int `json:"max_heap_size"` + } `json:"garbage_collection"` + State string `json:"state"` + GlobalPrefetchCount int `json:"global_prefetch_count"` + PrefetchCount int `json:"prefetch_count"` + AcksUncommitted int `json:"acks_uncommitted"` + MessagesUncommitted int `json:"messages_uncommitted"` + MessagesUnconfirmed int `json:"messages_unconfirmed"` + MessagesUnacknowledged int `json:"messages_unacknowledged"` + ConsumerCount int `json:"consumer_count"` + Confirm bool `json:"confirm"` + Transactional bool `json:"transactional"` +} + // RabbitOverview models the /overview resource of the rabbitmq http api type RabbitOverview struct { ManagementVersion string `json:"management_version"` @@ -77,7 +185,7 @@ type RabbitOverview struct { Protocol string `json:"protocol"` IPAddress string `json:"ip_address"` Port int `json:"port"` - // workaround for rabbitmq returnint empty array OR valid object + // workaround for rabbitmq returning empty array OR valid object // here. TODO check / further investigate.- /*Dummy []interface{} `json:"socket_opts,omitempty"` SocketOpts struct { @@ -215,7 +323,7 @@ type ChannelDetails struct { // UnmarshalJSON is a custom unmarshaler as a WORKAROUND for RabbitMQ API // returning "[]" instead of null. To make sure deserialization does not -// break, we catch this case, and return empty a ChannelDetails struct. +// break, we catch this case, and return an empty ChannelDetails struct. // see e.g. https://github.com/rabbitmq/rabbitmq-management/issues/424 func (d *ChannelDetails) UnmarshalJSON(data []byte) error { // akias ChannelDetails to avoid recursion when callung Unmarshal @@ -260,7 +368,7 @@ func (s *RabbitHTTPClient) getResource(uri string, result interface{}) error { } if resp.StatusCode != 200 { - return errors.New(resp.Status) + return errors.New(uri + " : " + resp.Status) } defer resp.Body.Close() @@ -274,36 +382,51 @@ func (s *RabbitHTTPClient) getResource(uri string, result interface{}) error { return nil } -// GetOverview returns the /overview ressource of the broker -func (s *RabbitHTTPClient) GetOverview() (RabbitOverview, error) { +// Connections returns the /connections ressource of the broker +func (s *RabbitHTTPClient) Connections() ([]RabbitConnection, error) { + var result []RabbitConnection + err := s.getResource(s.uri+"/connections", &result) + return result, err +} + +// Channels returns the /channels ressource of the broker +// not yet used. +// func (s *RabbitHTTPClient) Channels() ([]RabbitChannel, error) { +// var result []RabbitChannel +// err := s.getResource(s.uri+"/channels", &result) +// return result, err +// } + +// Overview returns the /overview ressource of the broker +func (s *RabbitHTTPClient) Overview() (RabbitOverview, error) { var result RabbitOverview err := s.getResource(s.uri+"/overview", &result) return result, err } -// GetExchanges returns the /exchanges ressource of the broker -func (s *RabbitHTTPClient) GetExchanges() ([]RabbitExchange, error) { +// Exchanges returns the /exchanges ressource of the broker +func (s *RabbitHTTPClient) Exchanges() ([]RabbitExchange, error) { var result []RabbitExchange err := s.getResource(s.uri+"/exchanges", &result) return result, err } -// GetQueues returns the /queues ressource of the broker -func (s *RabbitHTTPClient) GetQueues() ([]RabbitQueue, error) { +// Queues returns the /queues ressource of the broker +func (s *RabbitHTTPClient) Queues() ([]RabbitQueue, error) { var result []RabbitQueue err := s.getResource(s.uri+"/queues", &result) return result, err } -// GetConsumers returns the /consumers ressource of the broker -func (s *RabbitHTTPClient) GetConsumers() ([]RabbitConsumer, error) { +// Consumers returns the /consumers ressource of the broker +func (s *RabbitHTTPClient) Consumers() ([]RabbitConsumer, error) { var result []RabbitConsumer err := s.getResource(s.uri+"/consumers", &result) return result, err } -// GetBindings returns the /bindings ressource of the broker -func (s *RabbitHTTPClient) GetBindings() ([]RabbitBinding, error) { +// Bindings returns the /bindings ressource of the broker +func (s *RabbitHTTPClient) Bindings() ([]RabbitBinding, error) { var result []RabbitBinding err := s.getResource(s.uri+"/bindings", &result) return result, err diff --git a/pkg/rabbitmq_rest_client_test.go b/pkg/rabbitmq_rest_client_test.go index 17b3aff..32ea9b2 100644 --- a/pkg/rabbitmq_rest_client_test.go +++ b/pkg/rabbitmq_rest_client_test.go @@ -62,7 +62,7 @@ func TestRabbitClientGetExchanges(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - result, err := client.GetExchanges() + result, err := client.Exchanges() assert.Nil(t, err) assert.Equal(t, 12, len(result)) assert.Equal(t, "", (result)[0].Name) @@ -84,7 +84,7 @@ func TestRabbitClientGetQueues(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - result, err := client.GetQueues() + result, err := client.Queues() assert.Nil(t, err) assert.Equal(t, 8, len(result)) assert.Equal(t, "/", (result)[0].Vhost) @@ -99,7 +99,7 @@ func TestRabbitClientGetOverview(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - result, err := client.GetOverview() + result, err := client.Overview() assert.Nil(t, err) assert.Equal(t, "3.6.9", result.ManagementVersion) @@ -112,7 +112,7 @@ func TestRabbitClientGetBindings(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - _, err := client.GetBindings() + _, err := client.Bindings() assert.Nil(t, err) // TODO @@ -125,7 +125,7 @@ func TestRabbitClientGetConsumers(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - consumer, err := client.GetConsumers() + consumer, err := client.Consumers() assert.Nil(t, err) assert.Equal(t, 2, len(consumer)) assert.Equal(t, "some_consumer", consumer[0].ConsumerTag) @@ -133,6 +133,19 @@ func TestRabbitClientGetConsumers(t *testing.T) { } +// test of GET /api/consumers endpoint +func TestRabbitClientGetConnections(t *testing.T) { + + mock := testcommon.NewRabbitAPIMock(testcommon.MockModeStd) + defer mock.Close() + client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) + + conn, err := client.Connections() + assert.Nil(t, err) + assert.Equal(t, 1, len(conn)) + assert.Equal(t, "172.17.0.1:40874 -> 172.17.0.2:5672", conn[0].Name) +} + // test of GET /api/consumers endpoint workaround for empty channel_details func TestRabbitClientGetConsumersChannelDetailsIsEmptyArray(t *testing.T) { @@ -140,7 +153,7 @@ func TestRabbitClientGetConsumersChannelDetailsIsEmptyArray(t *testing.T) { defer mock.Close() client := NewRabbitHTTPClient(mock.URL, &tls.Config{}) - consumer, err := client.GetConsumers() + consumer, err := client.Consumers() assert.Nil(t, err) assert.Equal(t, 2, len(consumer)) diff --git a/pkg/testcommon/rabbitmq_rest_api_mock.go b/pkg/testcommon/rabbitmq_rest_api_mock.go index 9b1af06..1015591 100644 --- a/pkg/testcommon/rabbitmq_rest_api_mock.go +++ b/pkg/testcommon/rabbitmq_rest_api_mock.go @@ -60,6 +60,10 @@ func mockStdHandler(w http.ResponseWriter, r *http.Request) { result = overviewResult case "/consumers": result = consumerResult + case "/channels": + result = channelResult + case "/connections": + result = connectionResult default: w.WriteHeader(http.StatusNotFound) } @@ -1150,11 +1154,11 @@ var consumerResult = ` "channel_details": { "peer_host": "172.17.0.1", "peer_port": 58938, - "connection_name": "172.17.0.1:58938 -> 172.17.0.2:5672", + "connection_name": "172.17.0.1:40874 -> 172.17.0.2:5672", "user": "guest", "number": 2, "node": "rabbit@35b655845dfd", - "name": "172.17.0.1:58938 -> 172.17.0.2:5672 (2)" + "name" : "172.17.0.1:40874 -> 172.17.0.2:5672 (1)" }, "queue": { "vhost": "/", @@ -1176,3 +1180,116 @@ var consumerResult = ` } } ]` + +var channelResult = ` + +[ + { + "user" : "guest", + "garbage_collection" : { + "min_bin_vheap_size" : 46422, + "fullsweep_after" : 65535, + "min_heap_size" : 233, + "minor_gcs" : 23, + "max_heap_size" : 0 + }, + "confirm" : false, + "prefetch_count" : 0, + "messages_unconfirmed" : 0, + "vhost" : "/", + "messages_uncommitted" : 0, + "consumer_count" : 0, + "messages_unacknowledged" : 0, + "message_stats" : { + "confirm_details" : { + "rate" : 0 + }, + "publish" : 20680, + "return_unroutable" : 0, + "return_unroutable_details" : { + "rate" : 0 + }, + "publish_details" : { + "rate" : 72 + }, + "confirm" : 0 + }, + "global_prefetch_count" : 0, + "reductions" : 13649459, + "transactional" : false, + "reductions_details" : { + "rate" : 32643.2 + }, + "name" : "172.17.0.1:40874 -> 172.17.0.2:5672 (1)", + "acks_uncommitted" : 0, + "node" : "rabbit@ae1ad1477419", + "state" : "running", + "connection_details" : { + "name" : "172.17.0.1:40874 -> 172.17.0.2:5672", + "peer_port" : 40874, + "peer_host" : "172.17.0.1" + }, + "number" : 1 + } +] +` + +var connectionResult = ` +[ + { + "host" : "172.17.0.2", + "ssl" : false, + "recv_cnt" : 8136, + "channels" : 1, + "timeout" : 10, + "reductions" : 1968995, + "node" : "rabbit@ae1ad1477419", + "recv_oct" : 6238173, + "name" : "172.17.0.1:40874 -> 172.17.0.2:5672", + "port" : 5672, + "type" : "network", + "send_oct_details" : { + "rate" : 1.6 + }, + "vhost" : "/", + "client_properties" : { + "product" : "https://github.com/streadway/amqp", + "capabilities" : { + "connection.blocked" : true, + "consumer_cancel_notify" : true + }, + "version" : "β" + }, + "peer_cert_validity" : null, + "state" : "running", + "user" : "guest", + "protocol" : "AMQP 0-9-1", + "peer_host" : "172.17.0.1", + "channel_max" : 65535, + "send_oct" : 1094, + "recv_oct_details" : { + "rate" : 16524 + }, + "ssl_cipher" : null, + "auth_mechanism" : "PLAIN", + "send_pend" : 0, + "send_cnt" : 73, + "ssl_hash" : null, + "ssl_key_exchange" : null, + "peer_port" : 40874, + "connected_at" : 1524852240438, + "ssl_protocol" : null, + "peer_cert_issuer" : null, + "peer_cert_subject" : null, + "garbage_collection" : { + "fullsweep_after" : 65535, + "minor_gcs" : 152, + "max_heap_size" : 0, + "min_bin_vheap_size" : 46422, + "min_heap_size" : 233 + }, + "frame_max" : 131072, + "reductions_details" : { + "rate" : 5928.2 + } + }]`