diff --git a/README.md b/README.md index 416af0c..59ea35a 100644 --- a/README.md +++ b/README.md @@ -40,46 +40,76 @@ Example ### Metrics -#### Overview +#### Global -Total number of: -* channels -* connections -* consumers -* exchanges -* queues +metric | description +-------| ------------ +|up | Was the last scrape of rabbitmq successful.| +|channelsTotal | Total number of open channels| +|connectionsTotal | Total number of open connections| +|consumersTotal | Total number of message consumers| +|queuesTotal | Total number of queues in use| +|exchangesTotal | Total number of exchanges in use| -#### Queues -For each queue the number of: - -* messages_ready -* messages_unacknowledged -* messages -* messages_ready_ram -* messages_unacknowledged_ram -* messages_ram -* messages_persistent -* message_bytes -* message_bytes_ready -* message_bytes_unacknowledged -* message_bytes_ram -* message_bytes_persistent -* disk_reads -* disk_writes -* consumers -* consumer_utilisation -* memory -* messages_published_total -* messages_confirmend_total -* messages_delivered_total -* messages_delivered_noack_total -* messages_get_total -* messages_get_noack_total -* messages_redelivered_total -* messages_returned_total +#### Queues +Labels: vhost, queue + +##### Gauge + +metric | description +-------| ------------ +|queue_messages_ready|Number of messages ready to be delivered to clients.| +|queue_messages_unacknowledged|Number of messages delivered to clients but not yet acknowledged.| +|queue_messages|Sum of ready and unacknowledged messages (queue depth).| +|queue_messages_ready_ram|Number of messages from messages_ready which are resident in ram.| +|queue_messages_unacknowledged_ram|Number of messages from messages_unacknowledged which are resident in ram.| +|queue_messages_ram|Total number of messages which are resident in ram.| +|queue_messages_persistent|Total number of persistent messages in the queue (will always be 0 for transient queues).| +|queue_message_bytes|Sum of the size of all message bodies in the queue. This does not include the message properties (including headers) or any overhead.| +|queue_message_bytes_ready|Like message_bytes but counting only those messages ready to be delivered to clients.| +|queue_message_bytes_unacknowledged|Like message_bytes but counting only those messages delivered to clients but not yet acknowledged.| +|queue_message_bytes_ram|Like message_bytes but counting only those messages which are in RAM.| +|queue_message_bytes_persistent|Like message_bytes but counting only those messages which are persistent.| +|queue_consumers|Number of consumers.| +|queue_consumer_utilisation|Fraction of the time (between 0.0 and 1.0) that the queue is able to immediately deliver messages to consumers. This can be less than 1.0 if consumers are limited by network congestion or prefetch count.| +|queue_memory|Bytes of memory consumed by the Erlang process associated with the queue, including stack, heap and internal structures.| + +##### Counter + +metric | description +-------| ------------ +|queue_disk_reads|Total number of times messages have been read from disk by this queue since it started.| +|queue_disk_writes|Total number of times messages have been written to disk by this queue since it started.| +|queue_messages_published_total|Count of messages published.| +|queue_messages_confirmed_total|Count of messages confirmed. | +|queue_messages_delivered_total|Count of messages delivered in acknowledgement mode to consumers.| +|queue_messages_delivered_noack_total|Count of messages delivered in no-acknowledgement mode to consumers. | +|queue_messages_get_total|Count of messages delivered in acknowledgement mode in response to basic.get.| +|queue_messages_get_noack_total|Count of messages delivered in no-acknowledgement mode in response to basic.get.| +|queue_messages_redelivered_total|Count of subset of messages in deliver_get which had the redelivered flag set.| +|queue_messages_returned_total|Count of messages returned to publisher as unroutable.| + +#### Exchanges - Counter + +Labels: vhost, exchange + +metric | description +-------| ------------ +|exchange_messages_published_total|Count of messages published.| +|exchange_messages_published_in_total|Count of messages published in to an exchange, i.e. not taking account of routing.| +|exchange_messages_published_out_total|Count of messages published out of an exchange, i.e. taking account of routing.| +|exchange_messages_confirmed_total|Count of messages confirmed. | +|exchange_messages_delivered_total|Count of messages delivered in acknowledgement mode to consumers.| +|exchange_messages_delivered_noack_total|Count of messages delivered in no-acknowledgement mode to consumers. | +|exchange_messages_get_total|Count of messages delivered in acknowledgement mode in response to basic.get.| +|exchange_messages_get_noack_total|Count of messages delivered in no-acknowledgement mode in response to basic.get.| +|exchange_messages_ack_total|Count of messages delivered in acknowledgement mode in response to basic.get.| +|exchange_messages_redelivered_total|Count of subset of messages in deliver_get which had the redelivered flag set.| +|exchange_messages_returned_total|Count of messages returned to publisher as unroutable.| + ## Docker To create a docker image locally it is recommened to use the Makefile. diff --git a/exporter.go b/exporter.go index ef4affd..a14b739 100644 --- a/exporter.go +++ b/exporter.go @@ -13,6 +13,7 @@ type exporter struct { queueMetricsCounter map[string]*prometheus.CounterVec overviewMetrics map[string]prometheus.Gauge upMetric prometheus.Gauge + exchangeMetrics map[string]*prometheus.CounterVec } func newExporter() *exporter { @@ -21,14 +22,16 @@ func newExporter() *exporter { queueMetricsCounter: queueCounterVec, overviewMetrics: overviewMetricDescription, upMetric: upMetricDescription, + exchangeMetrics: exchangeCounterVec, } } func (e *exporter) fetchRabbit() { - rabbitMqOverviewData, overviewError := getOverviewMap(config) - rabbitMqQueueData, queueError := getQueueInfo(config) + rabbitMqOverviewData, overviewError := getMetricMap(config, "overview") + rabbitMqQueueData, queueError := getStatsInfo(config, "queues") + exchangeData, exchangeError := getStatsInfo(config, "exchanges") - if overviewError != nil || queueError != nil { + if overviewError != nil || queueError != nil || exchangeError != nil { e.upMetric.Set(0) } else { e.upMetric.Set(1) @@ -66,6 +69,17 @@ func (e *exporter) fetchRabbit() { } } + for key, countvec := range e.exchangeMetrics { + for _, exchange := range exchangeData { + if value, ok := exchange.metrics[key]; ok { + log.WithFields(log.Fields{"vhost": exchange.vhost, "exchange": exchange.name, "key": key, "value": value}).Debug("Set exchange metric for key") + countvec.WithLabelValues(exchange.vhost, exchange.name).Set(value) + } else { + //log.WithFields(log.Fields{"queue": queue, "key": key}).Warn("Queue data not found") + } + } + } + log.Info("Metrics updated successfully.") } @@ -80,6 +94,9 @@ func (e *exporter) Describe(ch chan<- *prometheus.Desc) { for _, countervec := range e.queueMetricsCounter { countervec.Describe(ch) } + for _, exchangeMetric := range e.exchangeMetrics { + exchangeMetric.Describe(ch) + } } func (e *exporter) Collect(ch chan<- prometheus.Metric) { @@ -92,6 +109,9 @@ func (e *exporter) Collect(ch chan<- prometheus.Metric) { for _, countvec := range e.queueMetricsCounter { countvec.Reset() } + for _, exchangeMetric := range e.exchangeMetrics { + exchangeMetric.Reset() + } e.fetchRabbit() @@ -108,5 +128,9 @@ func (e *exporter) Collect(ch chan<- prometheus.Metric) { countervec.Collect(ch) } + for _, exchangeMetric := range e.exchangeMetrics { + exchangeMetric.Collect(ch) + } + BuildInfo.Collect(ch) } diff --git a/exporter_test.go b/exporter_test.go index 20460b8..66c3ccd 100644 --- a/exporter_test.go +++ b/exporter_test.go @@ -12,8 +12,115 @@ import ( ) const ( - overviewTestData = `{"management_version":"3.5.1","rates_mode":"basic","exchange_types":[{"name":"topic","description":"AMQP topic exchange, as per the AMQP specification","enabled":true},{"name":"fanout","description":"AMQP fanout exchange, as per the AMQP specification","enabled":true},{"name":"direct","description":"AMQP direct exchange, as per the AMQP specification","enabled":true},{"name":"headers","description":"AMQP headers exchange, as per the AMQP specification","enabled":true}],"rabbitmq_version":"3.5.1","cluster_name":"my-rabbit@ae74c041248b","erlang_version":"17.5","erlang_full_version":"Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:2:2] [async-threads:30] [kernel-poll:true]","message_stats":{},"queue_totals":{"messages":48,"messages_details":{"rate":0.0},"messages_ready":48,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0}},"object_totals":{"consumers":0,"queues":4,"exchanges":8,"connections":0,"channels":0},"statistics_db_event_queue":0,"node":"my-rabbit@ae74c041248b","statistics_db_node":"my-rabbit@ae74c041248b","listeners":[{"node":"my-rabbit@ae74c041248b","protocol":"amqp","ip_address":"::","port":5672},{"node":"my-rabbit@ae74c041248b","protocol":"clustering","ip_address":"::","port":25672}],"contexts":[{"node":"my-rabbit@ae74c041248b","description":"RabbitMQ Management","path":"/","port":"15672"}]}` - queuesTestData = `[{"memory":16056,"message_stats":{"disk_writes":6,"disk_writes_details":{"rate":0.4},"publish":6,"publish_details":{"rate":0.4}},"messages":6,"messages_details":{"rate":0.4},"messages_ready":6,"messages_ready_details":{"rate":0.4},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 19:02:19","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":6,"messages_ready_ram":6,"messages_unacknowledged_ram":0,"messages_persistent":6,"message_bytes":30,"message_bytes_ready":30,"message_bytes_unacknowledged":0,"message_bytes_ram":30,"message_bytes_persistent":30,"disk_reads":0,"disk_writes":6,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":0,"q4":6,"len":6,"target_ram_count":"infinity","next_seq_id":6,"avg_ingress_rate":0.007658533940556533,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue1","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":55344,"message_stats":{"disk_reads":25,"disk_reads_details":{"rate":0.0}},"messages":25,"messages_details":{"rate":0.0},"messages_ready":25,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":25,"messages_ready_ram":25,"messages_unacknowledged_ram":0,"messages_persistent":25,"message_bytes":75,"message_bytes_ready":75,"message_bytes_unacknowledged":0,"message_bytes_ram":75,"message_bytes_persistent":75,"disk_reads":25,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":24,"q4":1,"len":25,"target_ram_count":"infinity","next_seq_id":16384,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue2","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":34648,"message_stats":{"disk_reads":23,"disk_reads_details":{"rate":0.0}},"messages":23,"messages_details":{"rate":0.0},"messages_ready":23,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":23,"messages_ready_ram":23,"messages_unacknowledged_ram":0,"messages_persistent":23,"message_bytes":207,"message_bytes_ready":207,"message_bytes_unacknowledged":0,"message_bytes_ram":207,"message_bytes_persistent":207,"disk_reads":23,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":22,"q4":1,"len":23,"target_ram_count":"infinity","next_seq_id":16384,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue3","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":13912,"messages":0,"messages_details":{"rate":0.0},"messages_ready":0,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":0,"messages_ready_ram":0,"messages_unacknowledged_ram":0,"messages_persistent":0,"message_bytes":0,"message_bytes_ready":0,"message_bytes_unacknowledged":0,"message_bytes_ram":0,"message_bytes_persistent":0,"disk_reads":0,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":0,"q4":0,"len":0,"target_ram_count":"infinity","next_seq_id":0,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue4","vhost":"vhost4","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"}]` + overviewTestData = `{"management_version":"3.5.1","rates_mode":"basic","exchange_types":[{"name":"topic","description":"AMQP topic exchange, as per the AMQP specification","enabled":true},{"name":"fanout","description":"AMQP fanout exchange, as per the AMQP specification","enabled":true},{"name":"direct","description":"AMQP direct exchange, as per the AMQP specification","enabled":true},{"name":"headers","description":"AMQP headers exchange, as per the AMQP specification","enabled":true}],"rabbitmq_version":"3.5.1","cluster_name":"my-rabbit@ae74c041248b","erlang_version":"17.5","erlang_full_version":"Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:2:2] [async-threads:30] [kernel-poll:true]","message_stats":{},"queue_totals":{"messages":48,"messages_details":{"rate":0.0},"messages_ready":48,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0}},"object_totals":{"consumers":0,"queues":4,"exchanges":8,"connections":0,"channels":0},"statistics_db_event_queue":0,"node":"my-rabbit@ae74c041248b","statistics_db_node":"my-rabbit@ae74c041248b","listeners":[{"node":"my-rabbit@ae74c041248b","protocol":"amqp","ip_address":"::","port":5672},{"node":"my-rabbit@ae74c041248b","protocol":"clustering","ip_address":"::","port":25672}],"contexts":[{"node":"my-rabbit@ae74c041248b","description":"RabbitMQ Management","path":"/","port":"15672"}]}` + queuesTestData = `[{"memory":16056,"message_stats":{"disk_writes":6,"disk_writes_details":{"rate":0.4},"publish":6,"publish_details":{"rate":0.4}},"messages":6,"messages_details":{"rate":0.4},"messages_ready":6,"messages_ready_details":{"rate":0.4},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 19:02:19","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":6,"messages_ready_ram":6,"messages_unacknowledged_ram":0,"messages_persistent":6,"message_bytes":30,"message_bytes_ready":30,"message_bytes_unacknowledged":0,"message_bytes_ram":30,"message_bytes_persistent":30,"disk_reads":0,"disk_writes":6,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":0,"q4":6,"len":6,"target_ram_count":"infinity","next_seq_id":6,"avg_ingress_rate":0.007658533940556533,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue1","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":55344,"message_stats":{"disk_reads":25,"disk_reads_details":{"rate":0.0}},"messages":25,"messages_details":{"rate":0.0},"messages_ready":25,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":25,"messages_ready_ram":25,"messages_unacknowledged_ram":0,"messages_persistent":25,"message_bytes":75,"message_bytes_ready":75,"message_bytes_unacknowledged":0,"message_bytes_ram":75,"message_bytes_persistent":75,"disk_reads":25,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":24,"q4":1,"len":25,"target_ram_count":"infinity","next_seq_id":16384,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue2","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":34648,"message_stats":{"disk_reads":23,"disk_reads_details":{"rate":0.0}},"messages":23,"messages_details":{"rate":0.0},"messages_ready":23,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":23,"messages_ready_ram":23,"messages_unacknowledged_ram":0,"messages_persistent":23,"message_bytes":207,"message_bytes_ready":207,"message_bytes_unacknowledged":0,"message_bytes_ram":207,"message_bytes_persistent":207,"disk_reads":23,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":22,"q4":1,"len":23,"target_ram_count":"infinity","next_seq_id":16384,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue3","vhost":"/","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"},{"memory":13912,"messages":0,"messages_details":{"rate":0.0},"messages_ready":0,"messages_ready_details":{"rate":0.0},"messages_unacknowledged":0,"messages_unacknowledged_details":{"rate":0.0},"idle_since":"2015-07-07 18:57:52","consumer_utilisation":"","policy":"","exclusive_consumer_tag":"","consumers":0,"recoverable_slaves":"","state":"running","messages_ram":0,"messages_ready_ram":0,"messages_unacknowledged_ram":0,"messages_persistent":0,"message_bytes":0,"message_bytes_ready":0,"message_bytes_unacknowledged":0,"message_bytes_ram":0,"message_bytes_persistent":0,"disk_reads":0,"disk_writes":0,"backing_queue_status":{"q1":0,"q2":0,"delta":["delta","undefined",0,"undefined"],"q3":0,"q4":0,"len":0,"target_ram_count":"infinity","next_seq_id":0,"avg_ingress_rate":0.0,"avg_egress_rate":0.0,"avg_ack_ingress_rate":0.0,"avg_ack_egress_rate":0.0},"name":"myQueue4","vhost":"vhost4","durable":true,"auto_delete":false,"arguments":{},"node":"my-rabbit@ae74c041248b"}]` + exchangeAPIResponse = `[{ + "name": "", + "vhost": "/", + "type": "direct", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "name": "amq.direct", + "vhost": "/", + "type": "direct", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "name": "amq.fanout", + "vhost": "/", + "type": "fanout", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "name": "amq.headers", + "vhost": "/", + "type": "headers", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "name": "amq.match", + "vhost": "/", + "type": "headers", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "name": "amq.rabbitmq.log", + "vhost": "/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": true, + "arguments": {} +}, { + "name": "amq.rabbitmq.trace", + "vhost": "/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": true, + "arguments": {} +}, { + "name": "amq.topic", + "vhost": "/", + "type": "topic", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}, { + "message_stats": { + "publish": 0, + "publish_details": { + "rate": 0.0 + }, + "publish_in": 5, + "publish_in_details": { + "rate": 0.0 + }, + "publish_out": 0, + "publish_out_details": { + "rate": 0.0 + }, + "ack": 0, + "ack_details": { + "rate": 0.0 + }, + "deliver_get": 0, + "deliver_get_details": { + "rate": 0.0 + }, + "confirm": 5, + "confirm_details": { + "rate": 0.0 + }, + "return_unroutable": 5, + "return_unroutable_details": { + "rate": 0.0 + }, + "redeliver": 0, + "redeliver_details": { + "rate": 0.0 + } + }, + "name": "myExchange", + "vhost": "/", + "type": "fanout", + "durable": true, + "auto_delete": false, + "internal": false, + "arguments": {} +}]` ) func expectSubstring(t *testing.T, body string, substr string) { @@ -30,6 +137,8 @@ func TestWholeApp(t *testing.T) { fmt.Fprintln(w, overviewTestData) } else if r.RequestURI == "/api/queues" { fmt.Fprintln(w, queuesTestData) + } else if r.RequestURI == "/api/exchanges" { + fmt.Fprintln(w, exchangeAPIResponse) } else { t.Errorf("Invalid request. URI=%v", r.RequestURI) fmt.Fprintf(w, "Invalid request. URI=%v", r.RequestURI) @@ -50,7 +159,7 @@ func TestWholeApp(t *testing.T) { t.Errorf("Home page didn't return %v", http.StatusOK) } body := w.Body.String() - //fmt.Println(body) + // fmt.Println(body) expectSubstring(t, body, `rabbitmq_exchangesTotal 8`) expectSubstring(t, body, `rabbitmq_queue_messages_ready{queue="myQueue2",vhost="/"} 25`) expectSubstring(t, body, `rabbitmq_queue_message_bytes_persistent{queue="myQueue3",vhost="/"} 207`) @@ -58,4 +167,5 @@ func TestWholeApp(t *testing.T) { expectSubstring(t, body, `rabbitmq_queue_messages_published_total{queue="myQueue1",vhost="/"} 6`) expectSubstring(t, body, `rabbitmq_queue_disk_writes{queue="myQueue1",vhost="/"} 6`) expectSubstring(t, body, `rabbitmq_up 1`) + expectSubstring(t, body, `rabbitmq_exchange_messages_published_in_total{exchange="myExchange",vhost="/"} 5`) } diff --git a/jsonmap.go b/jsonmap.go index b15a6cc..12b17e4 100644 --- a/jsonmap.go +++ b/jsonmap.go @@ -9,42 +9,42 @@ import ( //MetricMap maps name to float64 metric type MetricMap map[string]float64 -//QueueInfo describes a queue: its name, vhost it belongs to, and all associated metrics. -type QueueInfo struct { +//StatsInfo describes one statistic (queue or exchange): its name, vhost it belongs to, and all associated metrics. +type StatsInfo struct { name string vhost string metrics MetricMap } -//MakeQueueInfo creates a slice if QueueInfo from json input. Only keys with float values are mapped into `metrics`. -func MakeQueueInfo(d *json.Decoder) []QueueInfo { - queues := make([]QueueInfo, 0) +//MakeStatsInfo creates a slice of StatsInfo from json input. Only keys with float values are mapped into `metrics`. +func MakeStatsInfo(d *json.Decoder) []StatsInfo { + var statistics []StatsInfo var jsonArr []map[string]interface{} if d == nil { log.Error("JSON decoder not iniatilized") - return queues + return make([]StatsInfo, 0) } if err := d.Decode(&jsonArr); err != nil { log.WithField("error", err).Error("Error while decoding json") - return queues + return make([]StatsInfo, 0) } for _, el := range jsonArr { log.WithFields(log.Fields{"element": el, "vhost": el["vhost"], "name": el["name"]}).Debug("Iterate over array") if name, ok := el["name"]; ok { - queue := QueueInfo{} - queue.name = name.(string) + statsinfo := StatsInfo{} + statsinfo.name = name.(string) if vhost, ok := el["vhost"]; ok { - queue.vhost = vhost.(string) + statsinfo.vhost = vhost.(string) } - queue.metrics = make(MetricMap) - addFields(&queue.metrics, "", el) - queues = append(queues, queue) + statsinfo.metrics = make(MetricMap) + addFields(&statsinfo.metrics, "", el) + statistics = append(statistics, statsinfo) } } - return queues + return statistics } //MakeMap creates a map from json input. Only keys with float values are mapped. diff --git a/jsonmap_test.go b/jsonmap_test.go index 08115a3..2a062ba 100644 --- a/jsonmap_test.go +++ b/jsonmap_test.go @@ -12,14 +12,14 @@ func TestWithInvalidJSON(t *testing.T) { if mm := MakeMap(invalidJSONDecoder); mm == nil { t.Errorf("Json is invalid. Empty map should be returned. Value: %v", mm) } - if qi := MakeQueueInfo(invalidJSONDecoder); qi == nil { + if qi := MakeStatsInfo(invalidJSONDecoder); qi == nil { t.Errorf("Json is invalid. Empty map should be returned. Value: %v", qi) } if mm := MakeMap(nil); mm == nil { t.Errorf("Empty map should be returned. Value: %v", mm) } - if qi := MakeQueueInfo(nil); qi == nil { + if qi := MakeStatsInfo(nil); qi == nil { t.Errorf("Empty map should be returned.. Value: %v", qi) } } @@ -50,11 +50,11 @@ func TestMakeMap(t *testing.T) { checkMap(flMap, t, 0) } -func TestMakeQueueInfo(t *testing.T) { +func TestMakeStatsInfo(t *testing.T) { jsonArray := strings.NewReader(`[{"name":"q1", "FloatKey":14,"nes":{"ted":15}},{"name":"q2", "vhost":"foo", "FloatKey":24,"nes":{"ted":25}}]`) decoder := json.NewDecoder(jsonArray) - qinfo := MakeQueueInfo(decoder) + qinfo := MakeStatsInfo(decoder) if qinfo[0].name != "q1" { t.Errorf("unexpected qinfo name: %v", qinfo[0].name) } diff --git a/metrics.go b/metrics.go index 1f33730..4d19155 100644 --- a/metrics.go +++ b/metrics.go @@ -7,72 +7,87 @@ const ( ) var ( - queueLabelNames = []string{"vhost", "queue"} + queueLabels = []string{"vhost", "queue"} + exchangeLabels = []string{"vhost", "exchange"} - upMetricDescription = newMetric("up", "Was the last scrape of rabbitmq successful.") + upMetricDescription = newGauge("up", "Was the last scrape of rabbitmq successful.") overviewMetricDescription = map[string]prometheus.Gauge{ - "object_totals.channels": newMetric("channelsTotal", "Total number of open channels."), - "object_totals.connections": newMetric("connectionsTotal", "Total number of open connections."), - "object_totals.consumers": newMetric("consumersTotal", "Total number of message consumers."), - "object_totals.queues": newMetric("queuesTotal", "Total number of queues in use."), - "object_totals.exchanges": newMetric("exchangesTotal", "Total number of exchanges in use."), + "object_totals.channels": newGauge("channelsTotal", "Total number of open channels."), + "object_totals.connections": newGauge("connectionsTotal", "Total number of open connections."), + "object_totals.consumers": newGauge("consumersTotal", "Total number of message consumers."), + "object_totals.queues": newGauge("queuesTotal", "Total number of queues in use."), + "object_totals.exchanges": newGauge("exchangesTotal", "Total number of exchanges in use."), } queueGaugeVec = map[string]*prometheus.GaugeVec{ - "messages_ready": newQueueGaugeVec("messages_ready", "Number of messages ready to be delivered to clients."), - "messages_unacknowledged": newQueueGaugeVec("messages_unacknowledged", "Number of messages delivered to clients but not yet acknowledged."), - "messages": newQueueGaugeVec("messages", "Sum of ready and unacknowledged messages (queue depth)."), - "messages_ready_ram": newQueueGaugeVec("messages_ready_ram", "Number of messages from messages_ready which are resident in ram."), - "messages_unacknowledged_ram": newQueueGaugeVec("messages_unacknowledged_ram", "Number of messages from messages_unacknowledged which are resident in ram."), - "messages_ram": newQueueGaugeVec("messages_ram", "Total number of messages which are resident in ram."), - "messages_persistent": newQueueGaugeVec("messages_persistent", "Total number of persistent messages in the queue (will always be 0 for transient queues)."), - "message_bytes": newQueueGaugeVec("message_bytes", "Sum of the size of all message bodies in the queue. This does not include the message properties (including headers) or any overhead."), - "message_bytes_ready": newQueueGaugeVec("message_bytes_ready", "Like message_bytes but counting only those messages ready to be delivered to clients."), - "message_bytes_unacknowledged": newQueueGaugeVec("message_bytes_unacknowledged", "Like message_bytes but counting only those messages delivered to clients but not yet acknowledged."), - "message_bytes_ram": newQueueGaugeVec("message_bytes_ram", "Like message_bytes but counting only those messages which are in RAM."), - "message_bytes_persistent": newQueueGaugeVec("message_bytes_persistent", "Like message_bytes but counting only those messages which are persistent."), - "consumers": newQueueGaugeVec("consumers", "Number of consumers."), - "consumer_utilisation": newQueueGaugeVec("consumer_utilisation", "Fraction of the time (between 0.0 and 1.0) that the queue is able to immediately deliver messages to consumers. This can be less than 1.0 if consumers are limited by network congestion or prefetch count."), - "memory": newQueueGaugeVec("memory", "Bytes of memory consumed by the Erlang process associated with the queue, including stack, heap and internal structures."), + "messages_ready": newGaugeVec("queue_messages_ready", "Number of messages ready to be delivered to clients.", queueLabels), + "messages_unacknowledged": newGaugeVec("queue_messages_unacknowledged", "Number of messages delivered to clients but not yet acknowledged.", queueLabels), + "messages": newGaugeVec("queue_messages", "Sum of ready and unacknowledged messages (queue depth).", queueLabels), + "messages_ready_ram": newGaugeVec("queue_messages_ready_ram", "Number of messages from messages_ready which are resident in ram.", queueLabels), + "messages_unacknowledged_ram": newGaugeVec("queue_messages_unacknowledged_ram", "Number of messages from messages_unacknowledged which are resident in ram.", queueLabels), + "messages_ram": newGaugeVec("queue_messages_ram", "Total number of messages which are resident in ram.", queueLabels), + "messages_persistent": newGaugeVec("queue_messages_persistent", "Total number of persistent messages in the queue (will always be 0 for transient queues).", queueLabels), + "message_bytes": newGaugeVec("queue_message_bytes", "Sum of the size of all message bodies in the queue. This does not include the message properties (including headers) or any overhead.", queueLabels), + "message_bytes_ready": newGaugeVec("queue_message_bytes_ready", "Like message_bytes but counting only those messages ready to be delivered to clients.", queueLabels), + "message_bytes_unacknowledged": newGaugeVec("queue_message_bytes_unacknowledged", "Like message_bytes but counting only those messages delivered to clients but not yet acknowledged.", queueLabels), + "message_bytes_ram": newGaugeVec("queue_message_bytes_ram", "Like message_bytes but counting only those messages which are in RAM.", queueLabels), + "message_bytes_persistent": newGaugeVec("queue_message_bytes_persistent", "Like message_bytes but counting only those messages which are persistent.", queueLabels), + "consumers": newGaugeVec("queue_consumers", "Number of consumers.", queueLabels), + "consumer_utilisation": newGaugeVec("queue_consumer_utilisation", "Fraction of the time (between 0.0 and 1.0) that the queue is able to immediately deliver messages to consumers. This can be less than 1.0 if consumers are limited by network congestion or prefetch count.", queueLabels), + "memory": newGaugeVec("queue_memory", "Bytes of memory consumed by the Erlang process associated with the queue, including stack, heap and internal structures.", queueLabels), } queueCounterVec = map[string]*prometheus.CounterVec{ - "disk_reads": newQueueCounterVec("disk_reads", "Total number of times messages have been read from disk by this queue since it started."), - "disk_writes": newQueueCounterVec("disk_writes", "Total number of times messages have been written to disk by this queue since it started."), - "message_stats.publish": newQueueCounterVec("messages_published_total", "Count of messages published."), - "message_stats.confirm": newQueueCounterVec("messages_confirmed_total", "Count of messages confirmed. "), - "message_stats.deliver": newQueueCounterVec("messages_delivered_total", "Count of messages delivered in acknowledgement mode to consumers."), - "message_stats.deliver_noack": newQueueCounterVec("messages_delivered_noack_total", "Count of messages delivered in no-acknowledgement mode to consumers. "), - "message_stats.get": newQueueCounterVec("messages_get_total", "Count of messages delivered in acknowledgement mode in response to basic.get."), - "message_stats.get_noack": newQueueCounterVec("messages_get_noack_total", "Count of messages delivered in no-acknowledgement mode in response to basic.get."), - "message_stats.redeliver": newQueueCounterVec("messages_redelivered_total", "Count of subset of messages in deliver_get which had the redelivered flag set."), - "message_stats.return": newQueueCounterVec("messages_returned_total", "Count of messages returned to publisher as unroutable."), + "disk_reads": newCounterVec("queue_disk_reads", "Total number of times messages have been read from disk by this queue since it started.", queueLabels), + "disk_writes": newCounterVec("queue_disk_writes", "Total number of times messages have been written to disk by this queue since it started.", queueLabels), + "message_stats.publish": newCounterVec("queue_messages_published_total", "Count of messages published.", queueLabels), + "message_stats.confirm": newCounterVec("queue_messages_confirmed_total", "Count of messages confirmed. ", queueLabels), + "message_stats.deliver": newCounterVec("queue_messages_delivered_total", "Count of messages delivered in acknowledgement mode to consumers.", queueLabels), + "message_stats.deliver_noack": newCounterVec("queue_messages_delivered_noack_total", "Count of messages delivered in no-acknowledgement mode to consumers. ", queueLabels), + "message_stats.get": newCounterVec("queue_messages_get_total", "Count of messages delivered in acknowledgement mode in response to basic.get.", queueLabels), + "message_stats.get_noack": newCounterVec("queue_messages_get_noack_total", "Count of messages delivered in no-acknowledgement mode in response to basic.get.", queueLabels), + "message_stats.redeliver": newCounterVec("queue_messages_redelivered_total", "Count of subset of messages in deliver_get which had the redelivered flag set.", queueLabels), + "message_stats.return": newCounterVec("queue_messages_returned_total", "Count of messages returned to publisher as unroutable.", queueLabels), + } + + exchangeCounterVec = map[string]*prometheus.CounterVec{ + "message_stats.publish": newCounterVec("exchange_messages_published_total", "Count of messages published.", exchangeLabels), + "message_stats.publish_in": newCounterVec("exchange_messages_published_in_total", "Count of messages published in to an exchange, i.e. not taking account of routing.", exchangeLabels), + "message_stats.publish_out": newCounterVec("exchange_messages_published_out_total", "Count of messages published out of an exchange, i.e. taking account of routing.", exchangeLabels), + "message_stats.confirm": newCounterVec("exchange_messages_confirmed_total", "Count of messages confirmed. ", exchangeLabels), + "message_stats.deliver": newCounterVec("exchange_messages_delivered_total", "Count of messages delivered in acknowledgement mode to consumers.", exchangeLabels), + "message_stats.deliver_noack": newCounterVec("exchange_messages_delivered_noack_total", "Count of messages delivered in no-acknowledgement mode to consumers. ", exchangeLabels), + "message_stats.get": newCounterVec("exchange_messages_get_total", "Count of messages delivered in acknowledgement mode in response to basic.get.", exchangeLabels), + "message_stats.get_noack": newCounterVec("exchange_messages_get_noack_total", "Count of messages delivered in no-acknowledgement mode in response to basic.get.", exchangeLabels), + "message_stats.ack": newCounterVec("exchange_messages_ack_total", "Count of messages delivered in acknowledgement mode in response to basic.get.", exchangeLabels), + "message_stats.redeliver": newCounterVec("exchange_messages_redelivered_total", "Count of subset of messages in deliver_get which had the redelivered flag set.", exchangeLabels), + "message_stats.return_unroutable": newCounterVec("exchange_messages_returned_total", "Count of messages returned to publisher as unroutable.", exchangeLabels), } ) -func newQueueCounterVec(metricName string, docString string) *prometheus.CounterVec { +func newCounterVec(metricName string, docString string, labels []string) *prometheus.CounterVec { return prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, - Name: "queue_" + metricName, + Name: metricName, Help: docString, }, - queueLabelNames, + labels, ) } -func newQueueGaugeVec(metricName string, docString string) *prometheus.GaugeVec { +func newGaugeVec(metricName string, docString string, labels []string) *prometheus.GaugeVec { return prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, - Name: "queue_" + metricName, + Name: metricName, Help: docString, }, - queueLabelNames, + labels, ) } -func newMetric(metricName string, docString string) prometheus.Gauge { +func newGauge(metricName string, docString string) prometheus.Gauge { return prometheus.NewGauge( prometheus.GaugeOpts{ Namespace: namespace, diff --git a/rabbitClient.go b/rabbitClient.go index 44d1575..d2fb0e4 100644 --- a/rabbitClient.go +++ b/rabbitClient.go @@ -37,23 +37,23 @@ func loadMetrics(config rabbitExporterConfig, endpoint string) (*json.Decoder, e return json.NewDecoder(bytes.NewBuffer(body)), nil } -func getQueueInfo(config rabbitExporterConfig) ([]QueueInfo, error) { - var q []QueueInfo +func getStatsInfo(config rabbitExporterConfig, apiEndpoint string) ([]StatsInfo, error) { + var q []StatsInfo - d, err := loadMetrics(config, "queues") + d, err := loadMetrics(config, apiEndpoint) if err != nil { return q, err } - q = MakeQueueInfo(d) + q = MakeStatsInfo(d) return q, nil } -func getOverviewMap(config rabbitExporterConfig) (MetricMap, error) { +func getMetricMap(config rabbitExporterConfig, apiEndpoint string) (MetricMap, error) { var overview MetricMap - d, err := loadMetrics(config, "overview") + d, err := loadMetrics(config, apiEndpoint) if err != nil { return overview, err } diff --git a/rabbitClient_test.go b/rabbitClient_test.go index 1879a1a..29b5108 100644 --- a/rabbitClient_test.go +++ b/rabbitClient_test.go @@ -22,7 +22,7 @@ func createTestserver(result int, answer string) *httptest.Server { })) } -func TestOverview(t *testing.T) { +func TestGetMetricMap(t *testing.T) { // Test server that always responds with 200 code, and specific payload server := createTestserver(200, `{"nonFloat":"bob@example.com","float1":1.23456789101112,"number":2}`) defer server.Close() @@ -31,7 +31,7 @@ func TestOverview(t *testing.T) { RabbitURL: server.URL, } - overview, _ := getOverviewMap(*config) + overview, _ := getMetricMap(*config, "overview") expect(t, len(overview), 2) expect(t, overview["float1"], 1.23456789101112) @@ -45,7 +45,7 @@ func TestOverview(t *testing.T) { RabbitURL: errorServer.URL, } - overview, _ = getOverviewMap(*config) + overview, _ = getMetricMap(*config, "overview") expect(t, len(overview), 0) } @@ -59,7 +59,8 @@ func TestQueues(t *testing.T) { RabbitURL: server.URL, } - queues, _ := getQueueInfo(*config) + queues, err := getStatsInfo(*config, "queues") + expect(t, err, nil) expect(t, len(queues), 2) expect(t, queues[0].name, "Queue1") expect(t, queues[0].vhost, "") @@ -80,7 +81,51 @@ func TestQueues(t *testing.T) { RabbitURL: errorServer.URL, } - queues, _ = getQueueInfo(*config) - + queues, err = getStatsInfo(*config, "queues") + if err == nil { + t.Errorf("Request failed. An error was expected but not found") + } expect(t, len(queues), 0) } + +func TestExchanges(t *testing.T) { + + // Test server that always responds with 200 code, and specific payload + server := createTestserver(200, exchangeAPIResponse) + defer server.Close() + + config := &rabbitExporterConfig{ + RabbitURL: server.URL, + } + + exchanges, err := getStatsInfo(*config, "exchanges") + expect(t, err, nil) + expect(t, len(exchanges), 9) + expect(t, exchanges[0].name, "") + expect(t, exchanges[0].vhost, "/") + expect(t, exchanges[1].name, "amq.direct") + expect(t, exchanges[1].vhost, "/") + expect(t, len(exchanges[0].metrics), 0) + expect(t, len(exchanges[1].metrics), 0) + + expect(t, exchanges[8].name, "myExchange") + expect(t, exchanges[8].vhost, "/") + expect(t, exchanges[8].metrics["message_stats.confirm"], 5.0) + expect(t, exchanges[8].metrics["message_stats.publish_in"], 5.0) + expect(t, exchanges[8].metrics["message_stats.ack"], 0.0) + expect(t, exchanges[8].metrics["message_stats.return_unroutable"], 5.0) + + //Unknown error Server + errorServer := createTestserver(500, http.StatusText(500)) + defer errorServer.Close() + + config = &rabbitExporterConfig{ + RabbitURL: errorServer.URL, + } + + exchanges, err = getStatsInfo(*config, "exchanges") + if err == nil { + t.Errorf("Request failed. An error was expected but not found") + } + expect(t, len(exchanges), 0) +} diff --git a/version.go b/version.go index 4deb9b0..9f17cf9 100644 --- a/version.go +++ b/version.go @@ -3,9 +3,13 @@ package main import "github.com/prometheus/client_golang/prometheus" var ( - Version string - Revision string - Branch string + //Version of Rabbitmq Exporter is set during build. + Version string + //Revision of Rabbitmq Exporter is set during build. + Revision string + //Branch of Rabbitmq Exporter is set during build. + Branch string + //BuildDate of Rabbitmq Exporter is set during build. BuildDate string )