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

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package activemq
     4  
     5  import (
     6  	_ "embed"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/netdata/go.d.plugin/pkg/matcher"
    12  	"github.com/netdata/go.d.plugin/pkg/web"
    13  
    14  	"github.com/netdata/go.d.plugin/agent/module"
    15  )
    16  
    17  //go:embed "config_schema.json"
    18  var configSchema string
    19  
    20  func init() {
    21  	module.Register("activemq", module.Creator{
    22  		JobConfigSchema: configSchema,
    23  		Create:          func() module.Module { return New() },
    24  	})
    25  }
    26  
    27  const (
    28  	keyQueues   = "queues"
    29  	keyTopics   = "topics"
    30  	keyAdvisory = "Advisory"
    31  )
    32  
    33  var nameReplacer = strings.NewReplacer(".", "_", " ", "")
    34  
    35  const (
    36  	defaultMaxQueues   = 50
    37  	defaultMaxTopics   = 50
    38  	defaultURL         = "http://127.0.0.1:8161"
    39  	defaultHTTPTimeout = time.Second
    40  )
    41  
    42  // New creates Example with default values.
    43  func New() *ActiveMQ {
    44  	config := Config{
    45  		HTTP: web.HTTP{
    46  			Request: web.Request{
    47  				URL: defaultURL,
    48  			},
    49  			Client: web.Client{
    50  				Timeout: web.Duration{Duration: defaultHTTPTimeout},
    51  			},
    52  		},
    53  
    54  		MaxQueues: defaultMaxQueues,
    55  		MaxTopics: defaultMaxTopics,
    56  	}
    57  
    58  	return &ActiveMQ{
    59  		Config:       config,
    60  		charts:       &Charts{},
    61  		activeQueues: make(map[string]bool),
    62  		activeTopics: make(map[string]bool),
    63  	}
    64  }
    65  
    66  // Config is the ActiveMQ module configuration.
    67  type Config struct {
    68  	web.HTTP     `yaml:",inline"`
    69  	Webadmin     string `yaml:"webadmin"`
    70  	MaxQueues    int    `yaml:"max_queues"`
    71  	MaxTopics    int    `yaml:"max_topics"`
    72  	QueuesFilter string `yaml:"queues_filter"`
    73  	TopicsFilter string `yaml:"topics_filter"`
    74  }
    75  
    76  // ActiveMQ ActiveMQ module.
    77  type ActiveMQ struct {
    78  	module.Base
    79  	Config `yaml:",inline"`
    80  
    81  	apiClient    *apiClient
    82  	activeQueues map[string]bool
    83  	activeTopics map[string]bool
    84  	queuesFilter matcher.Matcher
    85  	topicsFilter matcher.Matcher
    86  	charts       *Charts
    87  }
    88  
    89  // Cleanup makes cleanup.
    90  func (ActiveMQ) Cleanup() {}
    91  
    92  // Init makes initialization.
    93  func (a *ActiveMQ) Init() bool {
    94  	if a.URL == "" {
    95  		a.Error("URL not set")
    96  		return false
    97  	}
    98  
    99  	if a.Webadmin == "" {
   100  		a.Error("webadmin root path is not set")
   101  		return false
   102  	}
   103  
   104  	if a.QueuesFilter != "" {
   105  		f, err := matcher.NewSimplePatternsMatcher(a.QueuesFilter)
   106  		if err != nil {
   107  			a.Errorf("error on creating queues filter : %v", err)
   108  			return false
   109  		}
   110  		a.queuesFilter = matcher.WithCache(f)
   111  	}
   112  
   113  	if a.TopicsFilter != "" {
   114  		f, err := matcher.NewSimplePatternsMatcher(a.TopicsFilter)
   115  		if err != nil {
   116  			a.Errorf("error on creating topics filter : %v", err)
   117  			return false
   118  		}
   119  		a.topicsFilter = matcher.WithCache(f)
   120  	}
   121  
   122  	client, err := web.NewHTTPClient(a.Client)
   123  	if err != nil {
   124  		a.Error(err)
   125  		return false
   126  	}
   127  
   128  	a.apiClient = newAPIClient(client, a.Request, a.Webadmin)
   129  
   130  	return true
   131  }
   132  
   133  // Check makes check.
   134  func (a *ActiveMQ) Check() bool {
   135  	return len(a.Collect()) > 0
   136  }
   137  
   138  // Charts creates Charts.
   139  func (a ActiveMQ) Charts() *Charts {
   140  	return a.charts
   141  }
   142  
   143  // Collect collects metrics.
   144  func (a *ActiveMQ) Collect() map[string]int64 {
   145  	metrics := make(map[string]int64)
   146  
   147  	var (
   148  		queues *queues
   149  		topics *topics
   150  		err    error
   151  	)
   152  
   153  	if queues, err = a.apiClient.getQueues(); err != nil {
   154  		a.Error(err)
   155  		return nil
   156  	}
   157  
   158  	if topics, err = a.apiClient.getTopics(); err != nil {
   159  		a.Error(err)
   160  		return nil
   161  	}
   162  
   163  	a.processQueues(queues, metrics)
   164  	a.processTopics(topics, metrics)
   165  
   166  	return metrics
   167  }
   168  
   169  func (a *ActiveMQ) processQueues(queues *queues, metrics map[string]int64) {
   170  	var (
   171  		count   = len(a.activeQueues)
   172  		updated = make(map[string]bool)
   173  		unp     int
   174  	)
   175  
   176  	for _, q := range queues.Items {
   177  		if strings.Contains(q.Name, keyAdvisory) {
   178  			continue
   179  		}
   180  
   181  		if !a.activeQueues[q.Name] {
   182  			if a.MaxQueues != 0 && count > a.MaxQueues {
   183  				unp++
   184  				continue
   185  			}
   186  
   187  			if !a.filterQueues(q.Name) {
   188  				continue
   189  			}
   190  
   191  			a.activeQueues[q.Name] = true
   192  			a.addQueueTopicCharts(q.Name, keyQueues)
   193  		}
   194  
   195  		rname := nameReplacer.Replace(q.Name)
   196  
   197  		metrics["queues_"+rname+"_consumers"] = q.Stats.ConsumerCount
   198  		metrics["queues_"+rname+"_enqueued"] = q.Stats.EnqueueCount
   199  		metrics["queues_"+rname+"_dequeued"] = q.Stats.DequeueCount
   200  		metrics["queues_"+rname+"_unprocessed"] = q.Stats.EnqueueCount - q.Stats.DequeueCount
   201  
   202  		updated[q.Name] = true
   203  	}
   204  
   205  	for name := range a.activeQueues {
   206  		if !updated[name] {
   207  			delete(a.activeQueues, name)
   208  			a.removeQueueTopicCharts(name, keyQueues)
   209  		}
   210  	}
   211  
   212  	if unp > 0 {
   213  		a.Debugf("%d queues were unprocessed due to max_queues limit (%d)", unp, a.MaxQueues)
   214  	}
   215  }
   216  
   217  func (a *ActiveMQ) processTopics(topics *topics, metrics map[string]int64) {
   218  	var (
   219  		count   = len(a.activeTopics)
   220  		updated = make(map[string]bool)
   221  		unp     int
   222  	)
   223  
   224  	for _, t := range topics.Items {
   225  		if strings.Contains(t.Name, keyAdvisory) {
   226  			continue
   227  		}
   228  
   229  		if !a.activeTopics[t.Name] {
   230  			if a.MaxTopics != 0 && count > a.MaxTopics {
   231  				unp++
   232  				continue
   233  			}
   234  
   235  			if !a.filterTopics(t.Name) {
   236  				continue
   237  			}
   238  
   239  			a.activeTopics[t.Name] = true
   240  			a.addQueueTopicCharts(t.Name, keyTopics)
   241  		}
   242  
   243  		rname := nameReplacer.Replace(t.Name)
   244  
   245  		metrics["topics_"+rname+"_consumers"] = t.Stats.ConsumerCount
   246  		metrics["topics_"+rname+"_enqueued"] = t.Stats.EnqueueCount
   247  		metrics["topics_"+rname+"_dequeued"] = t.Stats.DequeueCount
   248  		metrics["topics_"+rname+"_unprocessed"] = t.Stats.EnqueueCount - t.Stats.DequeueCount
   249  
   250  		updated[t.Name] = true
   251  	}
   252  
   253  	for name := range a.activeTopics {
   254  		if !updated[name] {
   255  			// TODO: delete after timeout?
   256  			delete(a.activeTopics, name)
   257  			a.removeQueueTopicCharts(name, keyTopics)
   258  		}
   259  	}
   260  
   261  	if unp > 0 {
   262  		a.Debugf("%d topics were unprocessed due to max_topics limit (%d)", unp, a.MaxTopics)
   263  	}
   264  }
   265  
   266  func (a ActiveMQ) filterQueues(line string) bool {
   267  	if a.queuesFilter == nil {
   268  		return true
   269  	}
   270  	return a.queuesFilter.MatchString(line)
   271  }
   272  
   273  func (a ActiveMQ) filterTopics(line string) bool {
   274  	if a.topicsFilter == nil {
   275  		return true
   276  	}
   277  	return a.topicsFilter.MatchString(line)
   278  }
   279  
   280  func (a *ActiveMQ) addQueueTopicCharts(name, typ string) {
   281  	rname := nameReplacer.Replace(name)
   282  
   283  	charts := charts.Copy()
   284  
   285  	for _, chart := range *charts {
   286  		chart.ID = fmt.Sprintf(chart.ID, typ, rname)
   287  		chart.Title = fmt.Sprintf(chart.Title, name)
   288  		chart.Fam = typ
   289  
   290  		for _, dim := range chart.Dims {
   291  			dim.ID = fmt.Sprintf(dim.ID, typ, rname)
   292  		}
   293  	}
   294  
   295  	_ = a.charts.Add(*charts...)
   296  
   297  }
   298  
   299  func (a *ActiveMQ) removeQueueTopicCharts(name, typ string) {
   300  	rname := nameReplacer.Replace(name)
   301  
   302  	chart := a.charts.Get(fmt.Sprintf("%s_%s_messages", typ, rname))
   303  	chart.MarkRemove()
   304  	chart.MarkNotCreated()
   305  
   306  	chart = a.charts.Get(fmt.Sprintf("%s_%s_unprocessed_messages", typ, rname))
   307  	chart.MarkRemove()
   308  	chart.MarkNotCreated()
   309  
   310  	chart = a.charts.Get(fmt.Sprintf("%s_%s_consumers", typ, rname))
   311  	chart.MarkRemove()
   312  	chart.MarkNotCreated()
   313  }