master
go 301 lines 8.09 KB
Raw
1 //go:build cgo
2
3 package mp
4
5 import (
6 "context"
7 "errors"
8 "math"
9 "strings"
10 "sync"
11
12 "github.com/netdata/netdata/go/plugins/pkg/matcher"
13 "github.com/netdata/netdata/go/plugins/pkg/prometheus"
14 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/framework"
15 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/common"
16 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/modules/websphere/mp/contexts"
17 "github.com/netdata/netdata/go/plugins/plugin/ibm.d/protocols/openmetrics"
18 )
19
20 // Collector implements the WebSphere MicroProfile module using the ibm.d framework.
21 type Collector struct {
22 framework.Collector
23
24 Config `yaml:",inline" json:",inline"`
25
26 once sync.Once
27
28 client *openmetrics.Client
29
30 identity common.Identity
31
32 restSelector matcher.Matcher
33
34 mpMetricsVersion string
35 serverType string
36 }
37
38 func (c *Collector) initOnce() {
39 c.once.Do(func() {
40 })
41 }
42
43 // CollectOnce performs a single scrape.
44 func (c *Collector) CollectOnce() error {
45 c.initOnce()
46 if c.client == nil {
47 return errors.New("openmetrics client not initialised")
48 }
49
50 timeout := c.Config.ClientConfig.Timeout.Duration()
51 if timeout <= 0 {
52 timeout = defaultTimeout
53 }
54
55 ctx, cancel := context.WithTimeout(context.Background(), timeout)
56 defer cancel()
57
58 series, err := c.client.FetchSeries(ctx, nil)
59 if err != nil {
60 return err
61 }
62
63 agg := make(map[string]int64)
64 restData := make(map[restKey]*restMetrics)
65
66 c.mpMetricsVersion = detectMetricsVersion(series)
67 if c.serverType == "" {
68 c.serverType = "Liberty MicroProfile"
69 }
70
71 for _, sample := range series {
72 name := sample.Labels.Get("__name__")
73 if name == "" {
74 continue
75 }
76
77 scope := sample.Labels.Get("mp_scope")
78 normalized := normalizeMetricName(name, scope)
79
80 method := sample.Labels.Get("method")
81 endpoint := sample.Labels.Get("endpoint")
82
83 if method != "" && endpoint != "" {
84 if !c.CollectRESTMetrics.IsEnabled() {
85 continue
86 }
87 c.collectRESTMetric(restData, method, endpoint, normalized, sample.Value)
88 continue
89 }
90
91 if !c.CollectJVMMetrics.IsEnabled() && isCoreMetric(normalized) {
92 continue
93 }
94
95 c.collectCoreMetric(agg, normalized, sample.Value)
96 }
97
98 c.exportCoreMetrics(agg)
99 c.exportRESTMetrics(restData)
100 c.Debugf("exported core metrics (keys=%d) rest endpoints=%d", len(agg), len(restData))
101
102 labels := c.identity.Labels()
103 if c.mpMetricsVersion != "" && c.mpMetricsVersion != "unknown" {
104 labels["mp_metrics_version"] = c.mpMetricsVersion
105 }
106 if c.serverType != "" {
107 labels["server_type"] = c.serverType
108 }
109 c.SetGlobalLabels(labels)
110
111 return nil
112 }
113
114 var _ framework.CollectorImpl = (*Collector)(nil)
115
116 type restKey struct {
117 Method string
118 Endpoint string
119 }
120
121 type restMetrics struct {
122 Requests int64
123 ResponseTime int64
124 hasRequests bool
125 hasResponse bool
126 }
127
128 func normalizeMetricName(name, scope string) string {
129 if scope != "" && !strings.HasPrefix(name, scope+"_") {
130 return scope + "_" + name
131 }
132 return name
133 }
134
135 func detectMetricsVersion(series prometheus.Series) string {
136 hasLabelScope := false
137 hasPrefix := false
138 hasVendor := false
139
140 for _, s := range series {
141 name := s.Labels.Get("__name__")
142 scope := s.Labels.Get("mp_scope")
143 if scope != "" {
144 hasLabelScope = true
145 if scope == "vendor" {
146 hasVendor = true
147 }
148 }
149 if strings.HasPrefix(name, "base_") || strings.HasPrefix(name, "vendor_") {
150 hasPrefix = true
151 }
152 if strings.HasPrefix(name, "vendor_") {
153 hasVendor = true
154 }
155 }
156
157 if hasLabelScope {
158 return "5.1"
159 }
160 if hasPrefix {
161 if hasVendor {
162 return "3.0"
163 }
164 return "4.0"
165 }
166 return "unknown"
167 }
168
169 func (c *Collector) collectCoreMetric(agg map[string]int64, name string, value float64) {
170 switch name {
171 case "base_memory_usedHeap_bytes":
172 agg["heap_used"] = int64(value)
173 case "base_memory_committedHeap_bytes":
174 agg["heap_committed"] = int64(value)
175 case "base_memory_maxHeap_bytes":
176 agg["heap_max"] = int64(value)
177 case "vendor_memory_heapUtilization_percent":
178 agg["heap_util"] = common.FormatPercent(value)
179 case "base_gc_total":
180 agg["gc_total"] = int64(value)
181 case "base_gc_time_seconds":
182 agg["gc_time_ms"] = secondsToMillis(value)
183 case "vendor_gc_time_per_cycle_seconds":
184 agg["gc_time_cycle_ms"] = secondsToMillis(value)
185 case "base_thread_count":
186 agg["thread_total"] = int64(value)
187 case "base_thread_daemon_count":
188 agg["thread_daemon"] = int64(value)
189 case "base_thread_max_count":
190 agg["thread_peak"] = int64(value)
191 case "base_cpu_processCpuLoad_percent":
192 agg["cpu_process"] = common.FormatPercent(value)
193 case "vendor_cpu_processCpuUtilization_percent":
194 agg["cpu_util"] = common.FormatPercent(value)
195 case "base_cpu_processCpuTime_seconds":
196 agg["cpu_time_ms"] = secondsToMillis(value)
197 case "threadpool_activeThreads":
198 agg["threadpool_active"] = int64(value)
199 case "threadpool_size":
200 agg["threadpool_size"] = int64(value)
201 }
202 }
203
204 func secondsToMillis(v float64) int64 {
205 return int64(math.Round(v * 1000))
206 }
207
208 func (c *Collector) collectRESTMetric(rest map[restKey]*restMetrics, method, endpoint, name string, value float64) {
209 target := method + " " + endpoint
210 if c.restSelector != nil && !c.restSelector.MatchString(target) {
211 return
212 }
213
214 key := restKey{Method: method, Endpoint: endpoint}
215 metrics := rest[key]
216 if metrics == nil {
217 if c.MaxRESTEndpoints > 0 && len(rest) >= c.MaxRESTEndpoints {
218 return
219 }
220 metrics = &restMetrics{}
221 rest[key] = metrics
222 }
223
224 lower := strings.ToLower(name)
225 switch {
226 case strings.Contains(lower, "request") && (strings.Contains(lower, "total") || strings.Contains(lower, "count")):
227 metrics.Requests = int64(value)
228 metrics.hasRequests = true
229 case strings.Contains(lower, "time") || strings.Contains(lower, "duration"):
230 metrics.ResponseTime = secondsToMillis(value)
231 metrics.hasResponse = true
232 }
233 }
234
235 func (c *Collector) exportCoreMetrics(agg map[string]int64) {
236 labels := contexts.EmptyLabels{}
237
238 used := agg["heap_used"]
239 committed := agg["heap_committed"]
240 free := max(committed-used, 0)
241 contexts.JVM.HeapUsage.Set(c.State, labels, contexts.JVMHeapUsageValues{
242 Used: used,
243 Free: free,
244 })
245 contexts.JVM.HeapCommitted.Set(c.State, labels, contexts.JVMHeapCommittedValues{Committed: committed})
246 contexts.JVM.HeapMax.Set(c.State, labels, contexts.JVMHeapMaxValues{Limit: agg["heap_max"]})
247 contexts.JVM.HeapUtilization.Set(c.State, labels, contexts.JVMHeapUtilizationValues{Utilization: agg["heap_util"]})
248
249 contexts.JVM.GCCollections.Set(c.State, labels, contexts.JVMGCCollectionsValues{Rate: agg["gc_total"]})
250 perCycle := agg["gc_time_cycle_ms"]
251 contexts.JVM.GCTime.Set(c.State, labels, contexts.JVMGCTimeValues{
252 Total: agg["gc_time_ms"],
253 Per_cycle: perCycle,
254 })
255
256 totalThreads := agg["thread_total"]
257 daemon := agg["thread_daemon"]
258 other := max(totalThreads-daemon, 0)
259 contexts.JVM.ThreadsCurrent.Set(c.State, labels, contexts.JVMThreadsCurrentValues{
260 Daemon: daemon,
261 Other: other,
262 })
263 contexts.JVM.ThreadsPeak.Set(c.State, labels, contexts.JVMThreadsPeakValues{Peak: agg["thread_peak"]})
264
265 contexts.CPU.Usage.Set(c.State, contexts.EmptyLabels{}, contexts.CPUUsageValues{
266 Process: agg["cpu_process"],
267 Utilization: agg["cpu_util"],
268 })
269 contexts.CPU.Time.Set(c.State, contexts.EmptyLabels{}, contexts.CPUTimeValues{Total: agg["cpu_time_ms"]})
270
271 active := agg["threadpool_active"]
272 size := agg["threadpool_size"]
273 idle := max(size-active, 0)
274 contexts.Vendor.ThreadPoolUsage.Set(c.State, labels, contexts.VendorThreadPoolUsageValues{
275 Active: active,
276 Idle: idle,
277 })
278 contexts.Vendor.ThreadPoolSize.Set(c.State, labels, contexts.VendorThreadPoolSizeValues{Size: size})
279 }
280
281 func (c *Collector) exportRESTMetrics(rest map[restKey]*restMetrics) {
282 for key, metrics := range rest {
283 labels := contexts.RESTEndpointLabels{Method: key.Method, Endpoint: key.Endpoint}
284 if metrics.hasRequests {
285 contexts.RESTEndpoint.Requests.Set(c.State, labels, contexts.RESTEndpointRequestsValues{Requests: metrics.Requests})
286 }
287 if metrics.hasResponse {
288 contexts.RESTEndpoint.ResponseTime.Set(c.State, labels, contexts.RESTEndpointResponseTimeValues{Average: metrics.ResponseTime})
289 }
290 }
291 }
292
293 func isCoreMetric(name string) bool {
294 if strings.HasPrefix(name, "base_") || strings.HasPrefix(name, "vendor_") {
295 return true
296 }
297 if strings.HasPrefix(name, "threadpool_") {
298 return true
299 }
300 return false
301 }