master
go 414 lines 11 KB
Raw
1 //go:build cgo
2
3 package jmx
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "strings"
10
11 "github.com/netdata/netdata/go/plugins/pkg/matcher"
12 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/framework"
13 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/common"
14 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/jmx/contexts"
15 jmxproto "github.com/netdata/netdata/go/plugins/plugin/ibm.d/protocols/websphere/jmx"
16 )
17
18 // Collector implements the WebSphere JMX module.
19 type Collector struct {
20 framework.Collector
21
22 Config Config
23
24 identity common.Identity
25 client jmxClient
26 poolSelector matcher.Matcher
27 jmsSelector matcher.Matcher
28 appSelector matcher.Matcher
29 }
30
31 // jmxClient abstracts the protocol client for easier testing.
32 type jmxClient interface {
33 Start(ctx context.Context) error
34 Shutdown()
35 FetchJVM(ctx context.Context) (*jmxproto.JVMStats, error)
36 FetchThreadPools(ctx context.Context, maxItems int) ([]jmxproto.ThreadPool, error)
37 FetchJDBCPools(ctx context.Context, maxItems int) ([]jmxproto.JDBCPool, error)
38 FetchJCAPools(ctx context.Context, maxItems int) ([]jmxproto.JCAPool, error)
39 FetchJMSDestinations(ctx context.Context, maxItems int) ([]jmxproto.JMSDestination, error)
40 FetchApplications(ctx context.Context, maxItems int, includeSessions, includeTransactions bool) ([]jmxproto.ApplicationMetric, error)
41 }
42
43 // CollectOnce performs a single scrape iteration.
44 func (c *Collector) CollectOnce() error {
45 if c.client == nil {
46 return errors.New("websphere_jmx: protocol client not initialised")
47 }
48
49 stats, err := c.client.FetchJVM(context.Background())
50 if err != nil {
51 return fmt.Errorf("websphere_jmx: fetch JVM metrics: %w", err)
52 }
53 if stats == nil {
54 return errors.New("websphere_jmx: empty JVM payload")
55 }
56 c.exportJVM(stats)
57
58 if c.Config.CollectThreadPoolMetrics.IsEnabled() {
59 if err := c.collectThreadPools(context.Background()); err != nil {
60 c.Warningf("failed to collect thread pool metrics: %v", err)
61 }
62 }
63
64 if c.Config.CollectJDBCMetrics.IsEnabled() {
65 if err := c.collectJDBCPools(context.Background()); err != nil {
66 c.Warningf("failed to collect JDBC metrics: %v", err)
67 }
68 }
69
70 if c.Config.CollectJCAMetrics.IsEnabled() {
71 if err := c.collectJCAPools(context.Background()); err != nil {
72 c.Warningf("failed to collect JCA metrics: %v", err)
73 }
74 }
75
76 if c.Config.CollectJMSMetrics.IsEnabled() {
77 if err := c.collectJMSDestinations(context.Background()); err != nil {
78 c.Warningf("failed to collect JMS metrics: %v", err)
79 }
80 }
81
82 if c.Config.CollectWebAppMetrics.IsEnabled() {
83 if err := c.collectApplications(context.Background()); err != nil {
84 c.Warningf("failed to collect application metrics: %v", err)
85 }
86 }
87
88 return nil
89 }
90
91 // Ensure Collector satisfies the framework contract.
92 var _ framework.CollectorImpl = (*Collector)(nil)
93
94 // exportJVM maps JVM statistics into framework contexts.
95 func (c *Collector) exportJVM(stats *jmxproto.JVMStats) {
96 labels := contexts.EmptyLabels{}
97
98 heapUsed := int64(stats.Heap.Used)
99 heapCommitted := int64(stats.Heap.Committed)
100 heapMax := int64(stats.Heap.Max)
101 contexts.JVM.HeapMemory.Set(c.State, labels, contexts.JVMHeapMemoryValues{
102 Used: heapUsed,
103 Committed: heapCommitted,
104 Max: heapMax,
105 })
106
107 var heapUsage int64
108 if stats.Heap.Max > 0 {
109 percentage := (stats.Heap.Used / stats.Heap.Max) * 100
110 heapUsage = common.FormatPercent(percentage)
111 }
112 contexts.JVM.HeapUsage.Set(c.State, labels, contexts.JVMHeapUsageValues{Usage: heapUsage})
113
114 contexts.JVM.NonHeapMemory.Set(c.State, labels, contexts.JVMNonHeapMemoryValues{
115 Used: int64(stats.NonHeap.Used),
116 Committed: int64(stats.NonHeap.Committed),
117 })
118
119 contexts.JVM.GCCycles.Set(c.State, labels, contexts.JVMGCCyclesValues{Collections: int64(stats.GC.Count)})
120 contexts.JVM.GCTime.Set(c.State, labels, contexts.JVMGCTimeValues{Time: int64(stats.GC.Time)})
121
122 contexts.JVM.Threads.Set(c.State, labels, contexts.JVMThreadsValues{
123 Total: int64(stats.Threads.Count),
124 Daemon: int64(stats.Threads.Daemon),
125 })
126
127 contexts.JVM.ThreadStates.Set(c.State, labels, contexts.JVMThreadStatesValues{
128 Peak: int64(stats.Threads.Peak),
129 Started: int64(stats.Threads.Started),
130 })
131
132 contexts.JVM.Classes.Set(c.State, labels, contexts.JVMClassesValues{
133 Loaded: int64(stats.Classes.Loaded),
134 Unloaded: int64(stats.Classes.Unloaded),
135 })
136
137 contexts.JVM.ProcessCPU.Set(c.State, labels, contexts.JVMProcessCPUValues{
138 Cpu: common.FormatPercent(stats.CPU.ProcessUsage),
139 })
140
141 contexts.JVM.Uptime.Set(c.State, labels, contexts.JVMUptimeValues{Uptime: int64(stats.Uptime)})
142 }
143
144 func (c *Collector) collectThreadPools(ctx context.Context) error {
145 max := c.Config.MaxThreadPools
146 pools, err := c.client.FetchThreadPools(ctx, max)
147 if err != nil {
148 return err
149 }
150
151 collected := 0
152 for _, pool := range pools {
153 name := strings.TrimSpace(pool.Name)
154 if name == "" {
155 continue
156 }
157
158 if c.poolSelector != nil && !c.poolSelector.MatchString(name) {
159 continue
160 }
161
162 if max > 0 && collected >= max {
163 break
164 }
165
166 labels := contexts.ThreadPoolsLabels{Pool: name}
167 contexts.ThreadPools.Size.Set(c.State, labels, contexts.ThreadPoolsSizeValues{
168 Size: int64(pool.PoolSize),
169 Max: int64(pool.MaximumPoolSize),
170 })
171 contexts.ThreadPools.Active.Set(c.State, labels, contexts.ThreadPoolsActiveValues{
172 Active: int64(pool.ActiveCount),
173 })
174
175 collected++
176 }
177
178 return nil
179 }
180
181 func (c *Collector) collectJDBCPools(ctx context.Context) error {
182 max := c.Config.MaxJDBCPools
183 pools, err := c.client.FetchJDBCPools(ctx, max)
184 if err != nil {
185 return err
186 }
187
188 collected := 0
189 const precision = 1000.0
190
191 for _, pool := range pools {
192 name := strings.TrimSpace(pool.Name)
193 if name == "" {
194 continue
195 }
196
197 if c.poolSelector != nil && !c.poolSelector.MatchString(name) {
198 continue
199 }
200
201 if max > 0 && collected >= max {
202 break
203 }
204
205 labels := contexts.JDBCLabels{Pool: name}
206
207 contexts.JDBC.PoolSize.Set(c.State, labels, contexts.JDBCPoolSizeValues{
208 Size: int64(pool.PoolSize),
209 })
210
211 contexts.JDBC.PoolUsage.Set(c.State, labels, contexts.JDBCPoolUsageValues{
212 Active: int64(pool.NumConnectionsUsed),
213 Free: int64(pool.NumConnectionsFree),
214 })
215
216 contexts.JDBC.WaitTime.Set(c.State, labels, contexts.JDBCWaitTimeValues{
217 Wait: int64(pool.AvgWaitTime * precision),
218 })
219
220 contexts.JDBC.UseTime.Set(c.State, labels, contexts.JDBCUseTimeValues{
221 Use: int64(pool.AvgInUseTime * precision),
222 })
223
224 contexts.JDBC.ConnectionsTotals.Set(c.State, labels, contexts.JDBCConnectionsTotalsValues{
225 Created: int64(pool.NumConnectionsCreated),
226 Destroyed: int64(pool.NumConnectionsDestroyed),
227 })
228
229 contexts.JDBC.WaitingThreads.Set(c.State, labels, contexts.JDBCWaitingThreadsValues{
230 Waiting: int64(pool.WaitingThreadCount),
231 })
232
233 collected++
234 }
235
236 return nil
237 }
238
239 func (c *Collector) collectJCAPools(ctx context.Context) error {
240 max := c.Config.MaxJCAPools
241 pools, err := c.client.FetchJCAPools(ctx, max)
242 if err != nil {
243 return err
244 }
245
246 const precision = 1000.0
247 collected := 0
248
249 for _, pool := range pools {
250 name := strings.TrimSpace(pool.Name)
251 if name == "" {
252 continue
253 }
254
255 if c.poolSelector != nil && !c.poolSelector.MatchString(name) {
256 continue
257 }
258
259 if max > 0 && collected >= max {
260 break
261 }
262
263 labels := contexts.JCALabels{Pool: name}
264
265 contexts.JCA.PoolSize.Set(c.State, labels, contexts.JCAPoolSizeValues{Size: int64(pool.PoolSize)})
266
267 contexts.JCA.PoolUsage.Set(c.State, labels, contexts.JCAPoolUsageValues{
268 Active: int64(pool.NumConnectionsUsed),
269 Free: int64(pool.NumConnectionsFree),
270 })
271
272 contexts.JCA.WaitTime.Set(c.State, labels, contexts.JCAWaitTimeValues{
273 Wait: int64(pool.AvgWaitTime * precision),
274 })
275
276 contexts.JCA.UseTime.Set(c.State, labels, contexts.JCAUseTimeValues{
277 Use: int64(pool.AvgInUseTime * precision),
278 })
279
280 contexts.JCA.ConnectionsTotals.Set(c.State, labels, contexts.JCAConnectionsTotalsValues{
281 Created: int64(pool.NumConnectionsCreated),
282 Destroyed: int64(pool.NumConnectionsDestroyed),
283 })
284
285 contexts.JCA.WaitingThreads.Set(c.State, labels, contexts.JCAWaitingThreadsValues{
286 Waiting: int64(pool.WaitingThreadCount),
287 })
288
289 collected++
290 }
291
292 return nil
293 }
294
295 func (c *Collector) collectJMSDestinations(ctx context.Context) error {
296 max := c.Config.MaxJMSDestinations
297 dests, err := c.client.FetchJMSDestinations(ctx, max)
298 if err != nil {
299 return err
300 }
301
302 collected := 0
303
304 for _, dest := range dests {
305 name := strings.TrimSpace(dest.Name)
306 if name == "" {
307 continue
308 }
309
310 if c.jmsSelector != nil && !c.jmsSelector.MatchString(name) {
311 continue
312 }
313
314 if max > 0 && collected >= max {
315 break
316 }
317
318 typeLabel := strings.ToLower(strings.TrimSpace(dest.Type))
319 if typeLabel == "" {
320 typeLabel = "unknown"
321 }
322
323 labels := contexts.JMSLabels{
324 Destination: name,
325 Destination_type: typeLabel,
326 }
327
328 contexts.JMS.MessagesCurrent.Set(c.State, labels, contexts.JMSMessagesCurrentValues{
329 Current: int64(dest.MessagesCurrentCount),
330 })
331
332 contexts.JMS.MessagesPending.Set(c.State, labels, contexts.JMSMessagesPendingValues{
333 Pending: int64(dest.MessagesPendingCount),
334 })
335
336 contexts.JMS.MessagesTotal.Set(c.State, labels, contexts.JMSMessagesTotalValues{
337 Total: int64(dest.MessagesAddedCount),
338 })
339
340 contexts.JMS.Consumers.Set(c.State, labels, contexts.JMSConsumersValues{
341 Consumers: int64(dest.ConsumerCount),
342 })
343
344 collected++
345 }
346
347 return nil
348 }
349
350 func (c *Collector) collectApplications(ctx context.Context) error {
351 max := c.Config.MaxApplications
352 apps, err := c.client.FetchApplications(ctx, max, c.Config.CollectSessionMetrics.IsEnabled(), c.Config.CollectTransactionMetrics.IsEnabled())
353 if err != nil {
354 return err
355 }
356
357 const precision = 1000.0
358 collected := 0
359
360 for _, app := range apps {
361 name := strings.TrimSpace(app.Name)
362 if name == "" {
363 continue
364 }
365
366 if c.appSelector != nil && !c.appSelector.MatchString(name) {
367 continue
368 }
369
370 if max > 0 && collected >= max {
371 break
372 }
373
374 moduleName := strings.TrimSpace(app.Module)
375 labels := contexts.ApplicationsLabels{
376 Application: name,
377 Module: moduleName,
378 }
379
380 contexts.Applications.Requests.Set(c.State, labels, contexts.ApplicationsRequestsValues{
381 Requests: int64(app.Requests),
382 })
383
384 contexts.Applications.ResponseTime.Set(c.State, labels, contexts.ApplicationsResponseTimeValues{
385 Response_time: int64(app.ResponseTime * precision),
386 })
387
388 if c.Config.CollectSessionMetrics.IsEnabled() {
389 contexts.Applications.SessionsActive.Set(c.State, labels, contexts.ApplicationsSessionsActiveValues{
390 Active: int64(app.ActiveSessions),
391 })
392
393 contexts.Applications.SessionsLive.Set(c.State, labels, contexts.ApplicationsSessionsLiveValues{
394 Live: int64(app.LiveSessions),
395 })
396
397 contexts.Applications.SessionEvents.Set(c.State, labels, contexts.ApplicationsSessionEventsValues{
398 Creates: int64(app.SessionCreates),
399 Invalidates: int64(app.SessionInvalidates),
400 })
401 }
402
403 if c.Config.CollectTransactionMetrics.IsEnabled() {
404 contexts.Applications.Transactions.Set(c.State, labels, contexts.ApplicationsTransactionsValues{
405 Committed: int64(app.TransactionsCommitted),
406 Rolledback: int64(app.TransactionsRolledback),
407 })
408 }
409
410 collected++
411 }
412
413 return nil
414 }