diff --git a/README.md b/README.md index b7be213a..efe17e9f 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,26 @@ Settings: * SKIPVERIFY: false or 0, true or 1 // will skip hostname/certificate check at all * INCLUDE_QUEUES: ".*", // regex, matching queue names are exported * SKIP_QUEUES: "^$", // regex, matching queue names are not exported (useful for short-lived rpc queues). First performed INCLUDE, after SKIP +* RABBIT_CAPABILITIES: "", // comma-separated list of extended scraping capabilities supported by the target RabbitMQ server Example OUTPUT_FORMAT=JSON PUBLISH_PORT=9099 ./rabbitmq_exporter +#### Extended RabbitMQ capabilities + +Newer version of RabbitMQ can provide some features that reduce +overhead imposed by scraping the data needed by this exporter. The +following capabilities are currently supported in +`RABBIT_CAPABILITIES` env var: + +* `no_sort`: By default RabbitMQ management plugin sorts results using + the default sort order of vhost/name. This sorting overhead can be + avoided by passing empty sort argument (`?sort=`) to RabbitMQ + starting from version 3.6.8. This option can be safely enabled on + earlier 3.6.X versions, but it'll not give any performance + improvements. And it's incompatible with 3.4.X and 3.5.X. + ### Metrics All metrics (except golang/prometheus metrics) are prefixed with "rabbitmq_". diff --git a/config.go b/config.go index be0e8f4f..6809c8ff 100644 --- a/config.go +++ b/config.go @@ -19,6 +19,7 @@ var ( InsecureSkipVerify: false, SkipQueues: "^$", IncludeQueues: ".*", + RabbitCapabilities: make(rabbitCapabilitySet), } ) @@ -32,6 +33,18 @@ type rabbitExporterConfig struct { InsecureSkipVerify bool SkipQueues string IncludeQueues string + RabbitCapabilities rabbitCapabilitySet +} + +type rabbitCapability string +type rabbitCapabilitySet map[rabbitCapability]bool + +const ( + rabbitCapNoSort rabbitCapability = "no_sort" +) + +var allRabbitCapabilities = rabbitCapabilitySet{ + rabbitCapNoSort: true, } func initConfig() { @@ -75,4 +88,21 @@ func initConfig() { if IncludeQueues := os.Getenv("INCLUDE_QUEUES"); IncludeQueues != "" { config.IncludeQueues = IncludeQueues } + + if rawCapabilities := os.Getenv("RABBIT_CAPABILITIES"); rawCapabilities != "" { + config.RabbitCapabilities = parseCapabilities(rawCapabilities) + } +} + +func parseCapabilities(raw string) rabbitCapabilitySet { + result := make(rabbitCapabilitySet) + candidates := strings.Split(raw, ",") + for _, maybeCapStr := range candidates { + maybeCap := rabbitCapability(strings.TrimSpace(maybeCapStr)) + enabled, present := allRabbitCapabilities[maybeCap] + if enabled && present { + result[maybeCap] = true + } + } + return result } diff --git a/config_test.go b/config_test.go index 27738014..6a4970b0 100644 --- a/config_test.go +++ b/config_test.go @@ -2,6 +2,7 @@ package main import ( "os" + "reflect" "testing" ) @@ -84,3 +85,23 @@ func TestConfig_Http_URL(t *testing.T) { t.Errorf("Invalid URL. It should start with http(s)://. expected=%v,got=%v", url, config.RabbitURL) } } + +func TestConfig_Capabilities(t *testing.T) { + defer os.Unsetenv("RABBIT_CAPABILITIES") + + os.Unsetenv("RABBIT_CAPABILITIES") + initConfig() + if !reflect.DeepEqual(config.RabbitCapabilities, make(rabbitCapabilitySet)) { + t.Error("Capability set should be empty by default") + } + + var needToSupport = []rabbitCapability{"no_sort"} + for _, cap := range needToSupport { + os.Setenv("RABBIT_CAPABILITIES", "junk_cap, another_with_spaces_around , "+string(cap)+", done") + initConfig() + expected := rabbitCapabilitySet{cap: true} + if !reflect.DeepEqual(config.RabbitCapabilities, expected) { + t.Errorf("Capability '%s' wasn't properly detected from env", cap) + } + } +} diff --git a/rabbitClient.go b/rabbitClient.go index 071945e3..896ae1fe 100644 --- a/rabbitClient.go +++ b/rabbitClient.go @@ -41,7 +41,13 @@ func initClient() { } func loadMetrics(config rabbitExporterConfig, endpoint string) (*json.Decoder, error) { - req, err := http.NewRequest("GET", config.RabbitURL+"/api/"+endpoint, nil) + var args string + enabled, exists := config.RabbitCapabilities[rabbitCapNoSort] + if enabled && exists { + args = "?sort=" + } + + req, err := http.NewRequest("GET", config.RabbitURL+"/api/"+endpoint+args, nil) req.SetBasicAuth(config.RabbitUsername, config.RabbitPassword) resp, err := client.Do(req) diff --git a/rabbitClient_test.go b/rabbitClient_test.go index 3b6d570a..26c82631 100644 --- a/rabbitClient_test.go +++ b/rabbitClient_test.go @@ -129,3 +129,35 @@ func TestExchanges(t *testing.T) { } expect(t, len(exchanges), 0) } + +func TestNoSort(t *testing.T) { + assertNoSortRespected(t, false) + assertNoSortRespected(t, true) +} + +func assertNoSortRespected(t *testing.T, enabled bool) { + var args string + if enabled { + args = "?sort=" + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + if r.RequestURI == "/api/overview"+args { + fmt.Fprintln(w, `{"nonFloat":"bob@example.com","float1":1.23456789101112,"number":2}`) + } else { + t.Errorf("Invalid request with enabled=%s. URI=%v", enabled, r.RequestURI) + fmt.Fprintf(w, "Invalid request. URI=%v", r.RequestURI) + } + + })) + defer server.Close() + + config := &rabbitExporterConfig{ + RabbitURL: server.URL, + RabbitCapabilities: rabbitCapabilitySet{rabbitCapNoSort: enabled}, + } + + getMetricMap(*config, "overview") +}