github.com/netdata/go.d.plugin@v0.58.1/modules/rabbitmq/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package rabbitmq
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"path/filepath"
    11  
    12  	"github.com/netdata/go.d.plugin/pkg/stm"
    13  	"github.com/netdata/go.d.plugin/pkg/web"
    14  )
    15  
    16  const (
    17  	urlPathAPIOverview = "/api/overview"
    18  	urlPathAPINodes    = "/api/nodes/"
    19  	urlPathAPIVhosts   = "/api/vhosts"
    20  	urlPathAPIQueues   = "/api/queues"
    21  )
    22  
    23  // TODO: there is built-in prometheus collector since v3.8.0 (https://www.rabbitmq.com/prometheus.html).
    24  // Should use it (in addition?), it is the recommended option  according to the docs.
    25  func (r *RabbitMQ) collect() (map[string]int64, error) {
    26  	mx := make(map[string]int64)
    27  
    28  	if err := r.collectOverviewStats(mx); err != nil {
    29  		return nil, err
    30  	}
    31  	if err := r.collectNodeStats(mx); err != nil {
    32  		return mx, err
    33  	}
    34  	if err := r.collectVhostsStats(mx); err != nil {
    35  		return mx, err
    36  	}
    37  	if r.CollectQueues {
    38  		if err := r.collectQueuesStats(mx); err != nil {
    39  			return mx, err
    40  		}
    41  	}
    42  
    43  	return mx, nil
    44  }
    45  
    46  func (r *RabbitMQ) collectOverviewStats(mx map[string]int64) error {
    47  	var stats overviewStats
    48  	if err := r.doOKDecode(urlPathAPIOverview, &stats); err != nil {
    49  		return err
    50  	}
    51  
    52  	if r.nodeName == "" {
    53  		r.nodeName = stats.Node
    54  	}
    55  
    56  	for k, v := range stm.ToMap(stats) {
    57  		mx[k] = v
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  func (r *RabbitMQ) collectNodeStats(mx map[string]int64) error {
    64  	if r.nodeName == "" {
    65  		return nil
    66  	}
    67  
    68  	var stats nodeStats
    69  	if err := r.doOKDecode(filepath.Join(urlPathAPINodes, r.nodeName), &stats); err != nil {
    70  		return err
    71  	}
    72  
    73  	for k, v := range stm.ToMap(stats) {
    74  		mx[k] = v
    75  	}
    76  	mx["proc_available"] = int64(stats.ProcTotal - stats.ProcUsed)
    77  
    78  	return nil
    79  }
    80  
    81  func (r *RabbitMQ) collectVhostsStats(mx map[string]int64) error {
    82  	var stats []vhostStats
    83  	if err := r.doOKDecode(urlPathAPIVhosts, &stats); err != nil {
    84  		return err
    85  	}
    86  
    87  	seen := make(map[string]bool)
    88  
    89  	for _, vhost := range stats {
    90  		seen[vhost.Name] = true
    91  		for k, v := range stm.ToMap(vhost) {
    92  			mx[fmt.Sprintf("vhost_%s_%s", vhost.Name, k)] = v
    93  		}
    94  	}
    95  
    96  	for name := range seen {
    97  		if !r.vhosts[name] {
    98  			r.vhosts[name] = true
    99  			r.Debugf("new vhost name='%s': creating charts", name)
   100  			r.addVhostCharts(name)
   101  		}
   102  	}
   103  	for name := range r.vhosts {
   104  		if !seen[name] {
   105  			delete(r.vhosts, name)
   106  			r.Debugf("stale vhost name='%s': removing charts", name)
   107  			r.removeVhostCharts(name)
   108  		}
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func (r *RabbitMQ) collectQueuesStats(mx map[string]int64) error {
   115  	var stats []queueStats
   116  	if err := r.doOKDecode(urlPathAPIQueues, &stats); err != nil {
   117  		return err
   118  	}
   119  
   120  	seen := make(map[string]queueCache)
   121  
   122  	for _, queue := range stats {
   123  		seen[queue.Name+"|"+queue.Vhost] = queueCache{name: queue.Name, vhost: queue.Vhost}
   124  		for k, v := range stm.ToMap(queue) {
   125  			mx[fmt.Sprintf("queue_%s_vhost_%s_%s", queue.Name, queue.Vhost, k)] = v
   126  		}
   127  	}
   128  
   129  	for key, queue := range seen {
   130  		if _, ok := r.queues[key]; !ok {
   131  			r.queues[key] = queue
   132  			r.Debugf("new queue name='%s', vhost='%s': creating charts", queue.name, queue.vhost)
   133  			r.addQueueCharts(queue.name, queue.vhost)
   134  		}
   135  	}
   136  	for key, queue := range r.queues {
   137  		if _, ok := seen[key]; !ok {
   138  			delete(r.queues, key)
   139  			r.Debugf("stale queue name='%s', vhost='%s': removing charts", queue.name, queue.vhost)
   140  			r.removeQueueCharts(queue.name, queue.vhost)
   141  		}
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  func (r *RabbitMQ) doOKDecode(urlPath string, in interface{}) error {
   148  	req, err := web.NewHTTPRequest(r.Request.Copy())
   149  	if err != nil {
   150  		return fmt.Errorf("error on creating request: %v", err)
   151  	}
   152  
   153  	req.URL.Path = urlPath
   154  
   155  	r.Debugf("doing HTTP %s to '%s'", req.Method, req.URL)
   156  	resp, err := r.httpClient.Do(req)
   157  	if err != nil {
   158  		return fmt.Errorf("error on request to %s: %v", req.URL, err)
   159  	}
   160  
   161  	defer closeBody(resp)
   162  
   163  	if resp.StatusCode != http.StatusOK {
   164  		return fmt.Errorf("%s returned HTTP status %d (%s)", req.URL, resp.StatusCode, resp.Status)
   165  	}
   166  
   167  	if err = json.NewDecoder(resp.Body).Decode(&in); err != nil {
   168  		return fmt.Errorf("error on decoding response from %s: %v", req.URL, err)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func closeBody(resp *http.Response) {
   175  	if resp != nil && resp.Body != nil {
   176  		_, _ = io.Copy(io.Discard, resp.Body)
   177  		_ = resp.Body.Close()
   178  	}
   179  }