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 }