├── .gitignore ├── Dockerfile ├── config.json ├── README.md ├── LICENSE.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | dependencies-stamp 3 | rabbitmq_exporter 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:onbuild 2 | MAINTAINER Michal Kobus 3 | EXPOSE 9672 4 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "9672", 3 | "req_interval": "15s", 4 | "nodes": [ 5 | { 6 | "name": "rabbitmq", 7 | "req_interval": "10s", 8 | "url": "http://127.0.0.1:15672", 9 | "uname": "guest", 10 | "password": "guest" 11 | }, 12 | { 13 | "name": "rabbitmq2", 14 | "req_interval": "10s", 15 | "url": "http://127.0.0.1:15673", 16 | "uname": "guest", 17 | "password": "guest" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ Exporter 2 | 3 | Prometheus exporter for RabbitMQ metrics, based on RabbitMQ HTTP API. 4 | 5 | ### Dependencies 6 | 7 | * Prometheus [client](https://github.com/prometheus/client_golang) for Golang 8 | * [Logging](https://github.com/Sirupsen/logrus) 9 | 10 | ### Setting up locally 11 | 12 | 1. You need **RabbitMQ**. For local setup I recommend this [docker box](https://github.com/mikaelhg/docker-rabbitmq). It's "one-click" solution. 13 | 14 | 2. For OS-specific **Docker** installation checkout these [instructions](https://docs.docker.com/installation/). 15 | 16 | 3. Building rabbitmq_exporter: 17 | 18 | $ docker build -t rabbitmq_exporter . 19 | 20 | 4. Running: 21 | 22 | $ docker run --publish 6060:9672 --rm rabbitmq_exporter 23 | 24 | Now your metrics are available through [http://localhost:6060/metrics](http://localhost:6060/metrics). 25 | 26 | ### Metrics 27 | 28 | Total number of: 29 | 30 | * channels 31 | * connections 32 | * consumers 33 | * exchanges 34 | * queues 35 | * messages 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Syncano 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 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/Sirupsen/logrus" 11 | "github.com/prometheus/client_golang/prometheus" 12 | ) 13 | 14 | const ( 15 | namespace = "rabbitmq" 16 | defaultConfigPath = "config.json" 17 | ) 18 | 19 | var log = logrus.New() 20 | 21 | // Listed available metrics 22 | var ( 23 | connectionsTotal = prometheus.NewGaugeVec( 24 | prometheus.GaugeOpts{ 25 | Namespace: namespace, 26 | Name: "connections_total", 27 | Help: "Total number of open connections.", 28 | }, 29 | []string{ 30 | // Which node was checked? 31 | "node", 32 | }, 33 | ) 34 | channelsTotal = prometheus.NewGaugeVec( 35 | prometheus.GaugeOpts{ 36 | Namespace: namespace, 37 | Name: "channels_total", 38 | Help: "Total number of open channels.", 39 | }, 40 | []string{ 41 | "node", 42 | }, 43 | ) 44 | queuesTotal = prometheus.NewGaugeVec( 45 | prometheus.GaugeOpts{ 46 | Namespace: namespace, 47 | Name: "queues_total", 48 | Help: "Total number of queues in use.", 49 | }, 50 | []string{ 51 | "node", 52 | }, 53 | ) 54 | consumersTotal = prometheus.NewGaugeVec( 55 | prometheus.GaugeOpts{ 56 | Namespace: namespace, 57 | Name: "consumers_total", 58 | Help: "Total number of message consumers.", 59 | }, 60 | []string{ 61 | "node", 62 | }, 63 | ) 64 | exchangesTotal = prometheus.NewGaugeVec( 65 | prometheus.GaugeOpts{ 66 | Namespace: namespace, 67 | Name: "exchanges_total", 68 | Help: "Total number of exchanges in use.", 69 | }, 70 | []string{ 71 | "node", 72 | }, 73 | ) 74 | messagesTotal = prometheus.NewGaugeVec( 75 | prometheus.GaugeOpts{ 76 | Namespace: namespace, 77 | Name: "messages_total", 78 | Help: "Total number of messages in all queues.", 79 | }, 80 | []string{ 81 | "node", 82 | }, 83 | ) 84 | ) 85 | 86 | type Config struct { 87 | Nodes *[]Node `json:"nodes"` 88 | Port string `json:"port"` 89 | Interval string `json:"req_interval"` 90 | } 91 | 92 | type Node struct { 93 | Name string `json:"name"` 94 | Url string `json:"url"` 95 | Uname string `json:"uname"` 96 | Password string `json:"password"` 97 | Interval string `json:"req_interval,omitempty"` 98 | } 99 | 100 | func sendApiRequest(hostname, username, password, query string) *json.Decoder { 101 | client := &http.Client{} 102 | req, err := http.NewRequest("GET", hostname+query, nil) 103 | req.SetBasicAuth(username, password) 104 | 105 | resp, err := client.Do(req) 106 | 107 | if err != nil { 108 | log.Error(err) 109 | panic(err) 110 | } 111 | return json.NewDecoder(resp.Body) 112 | } 113 | 114 | func getOverview(hostname, username, password string) { 115 | decoder := sendApiRequest(hostname, username, password, "/api/overview") 116 | response := decodeObj(decoder) 117 | 118 | metrics := make(map[string]float64) 119 | for k, v := range response["object_totals"].(map[string]interface{}) { 120 | metrics[k] = v.(float64) 121 | } 122 | nodename, _ := response["node"].(string) 123 | 124 | channelsTotal.WithLabelValues(nodename).Set(metrics["channels"]) 125 | connectionsTotal.WithLabelValues(nodename).Set(metrics["connections"]) 126 | consumersTotal.WithLabelValues(nodename).Set(metrics["consumers"]) 127 | queuesTotal.WithLabelValues(nodename).Set(metrics["queues"]) 128 | exchangesTotal.WithLabelValues(nodename).Set(metrics["exchanges"]) 129 | } 130 | 131 | func getNumberOfMessages(hostname, username, password string) { 132 | decoder := sendApiRequest(hostname, username, password, "/api/queues") 133 | response := decodeObjArray(decoder) 134 | nodename := response[0]["node"].(string) 135 | 136 | total_messages := 0.0 137 | for _, v := range response { 138 | total_messages += v["messages"].(float64) 139 | } 140 | messagesTotal.WithLabelValues(nodename).Set(total_messages) 141 | } 142 | 143 | func decodeObj(d *json.Decoder) map[string]interface{} { 144 | var response map[string]interface{} 145 | 146 | if err := d.Decode(&response); err != nil { 147 | log.Error(err) 148 | } 149 | return response 150 | } 151 | 152 | func decodeObjArray(d *json.Decoder) []map[string]interface{} { 153 | var response []map[string]interface{} 154 | 155 | if err := d.Decode(&response); err != nil { 156 | log.Error(err) 157 | } 158 | return response 159 | } 160 | 161 | func updateNodesStats(config *Config) { 162 | for _, node := range *config.Nodes { 163 | 164 | if len(node.Interval) == 0 { 165 | node.Interval = config.Interval 166 | } 167 | go runRequestLoop(node) 168 | } 169 | } 170 | 171 | func requestData(node Node) { 172 | defer func() { 173 | if r := recover(); r != nil { 174 | dt := 10 * time.Second 175 | time.Sleep(dt) 176 | } 177 | }() 178 | 179 | getOverview(node.Url, node.Uname, node.Password) 180 | getNumberOfMessages(node.Url, node.Uname, node.Password) 181 | 182 | log.Info("Metrics updated successfully.") 183 | 184 | dt, err := time.ParseDuration(node.Interval) 185 | if err != nil { 186 | log.Warn(err) 187 | dt = 30 * time.Second 188 | } 189 | time.Sleep(dt) 190 | } 191 | 192 | func runRequestLoop(node Node) { 193 | for { 194 | requestData(node) 195 | } 196 | } 197 | 198 | func loadConfig(path string, c *Config) bool { 199 | defer func() { 200 | if r := recover(); r != nil { 201 | dt := 10 * time.Second 202 | time.Sleep(dt) 203 | } 204 | }() 205 | 206 | file, err := ioutil.ReadFile(path) 207 | if err != nil { 208 | log.Error(err) 209 | panic(err) 210 | } 211 | 212 | err = json.Unmarshal(file, c) 213 | if err != nil { 214 | log.Error(err) 215 | panic(err) 216 | } 217 | return true 218 | } 219 | 220 | func runLoadConfigLoop(path string, c *Config) { 221 | for { 222 | is_ok := loadConfig(path, c) 223 | if is_ok == true { 224 | break 225 | } 226 | } 227 | } 228 | 229 | func main() { 230 | configPath := defaultConfigPath 231 | if len(os.Args) > 1 { 232 | configPath = os.Args[1] 233 | } 234 | log.Out = os.Stdout 235 | 236 | var config Config 237 | 238 | runLoadConfigLoop(configPath, &config) 239 | updateNodesStats(&config) 240 | 241 | http.Handle("/metrics", prometheus.Handler()) 242 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 243 | w.Write([]byte(` 244 | RabbitMQ Exporter 245 | 246 |

RabbitMQ Exporter

247 |

Metrics

248 | 249 | `)) 250 | }) 251 | log.Infof("Starting RabbitMQ exporter on port: %s.", config.Port) 252 | http.ListenAndServe(":"+config.Port, nil) 253 | } 254 | 255 | // Register metrics to Prometheus 256 | func init() { 257 | prometheus.MustRegister(channelsTotal) 258 | prometheus.MustRegister(connectionsTotal) 259 | prometheus.MustRegister(queuesTotal) 260 | prometheus.MustRegister(exchangesTotal) 261 | prometheus.MustRegister(consumersTotal) 262 | prometheus.MustRegister(messagesTotal) 263 | } 264 | --------------------------------------------------------------------------------