master
go 366 lines 9.49 KB
Raw
1 //go:build cgo
2
3 package as400
4
5 // SPDX-License-Identifier: GPL-3.0-or-later
6
7 import (
8 "context"
9 "fmt"
10 "strconv"
11 "strings"
12
13 as400proto "github.com/netdata/netdata/go/plugins/plugin/ibm.d/protocols/as400"
14 )
15
16 func cleanName(name string) string {
17 r := strings.NewReplacer(
18 " ", "_",
19 ".", "_",
20 "-", "_",
21 "/", "_",
22 ":", "_",
23 "=", "_",
24 )
25 return strings.ToLower(r.Replace(name))
26 }
27
28 func (c *Collector) logOnce(key string, format string, args ...any) {
29 if c.disabled[key] {
30 return
31 }
32 c.Warningf(format, args...)
33 c.disabled[key] = true
34 }
35
36 func (c *Collector) logErrorOnce(key string, format string, args ...any) {
37 c.muErrorLog.Lock()
38 defer c.muErrorLog.Unlock()
39
40 if c.errorLogged[key] {
41 return
42 }
43 msg := fmt.Sprintf(format, args...)
44 c.Errorf("[%s] %s", key, msg)
45 c.errorLogged[key] = true
46 }
47
48 func (c *Collector) clearErrorOnce(key string) {
49 c.muErrorLog.Lock()
50 defer c.muErrorLog.Unlock()
51
52 delete(c.errorLogged, key)
53 }
54
55 func (c *Collector) isDisabled(key string) bool {
56 return c.disabled[key]
57 }
58
59 func isSQLFeatureError(err error) bool {
60 return as400proto.IsFeatureError(err)
61 }
62
63 func isSQLTemporaryError(err error) bool {
64 return as400proto.IsTemporaryError(err)
65 }
66
67 func (c *Collector) collectSingleMetric(ctx context.Context, metricKey string, query string, handler func(value string)) error {
68 if c.isDisabled(metricKey) {
69 return nil
70 }
71
72 err := c.doQuery(ctx, metricKey, query, func(column, value string, lineEnd bool) {
73 if value != "" {
74 handler(value)
75 }
76 })
77
78 if err != nil && isSQLFeatureError(err) {
79 c.logOnce(metricKey, "metric %s not available on this IBM i version: %v", metricKey, err)
80 c.disabled[metricKey] = true
81 return nil
82 }
83
84 return err
85 }
86
87 func (c *Collector) detectIBMiVersion(ctx context.Context) error {
88 var version, release string
89 versionDetected := false
90
91 err := c.doQuery(ctx, "detect_ibmi_version_primary", queryIBMiVersion, func(column, value string, lineEnd bool) {
92 switch column {
93 case "OS_NAME":
94 // ignore
95 case "OS_VERSION":
96 version = strings.TrimSpace(value)
97 case "OS_RELEASE":
98 release = strings.TrimSpace(value)
99 }
100 })
101
102 if err == nil && version != "" && release != "" {
103 versionDetected = true
104 c.osVersion = fmt.Sprintf("%s.%s", version, release)
105 c.Debugf("detected IBM i version from ENV_SYS_INFO: %s", c.osVersion)
106 } else if err != nil {
107 c.Debugf("ENV_SYS_INFO query failed: %v, trying fallback method", err)
108
109 err = c.doQuery(ctx, "detect_ibmi_version_fallback", queryIBMiVersionDataArea, func(column, value string, lineEnd bool) {
110 if column == "VERSION" {
111 dataAreaValue := strings.TrimSpace(value)
112 if len(dataAreaValue) >= 6 {
113 c.osVersion = dataAreaValue
114 versionDetected = true
115 c.Debugf("detected IBM i version from data area: %s", c.osVersion)
116 }
117 }
118 })
119
120 if err != nil {
121 c.Warningf("failed to detect IBM i version from data area: %v", err)
122 }
123 }
124
125 if !versionDetected {
126 c.osVersion = "7.4"
127 c.Warningf("could not detect IBM i version, using default: %s", c.osVersion)
128 }
129
130 c.parseIBMiVersion()
131 return nil
132 }
133
134 func (c *Collector) detectAvailableFeatures(ctx context.Context) {
135 c.disabled["active_job_info"] = true
136 c.disabled["ifs_object_statistics"] = true
137 }
138
139 func trimDriverMessage(err error) string {
140 if err == nil {
141 return ""
142 }
143 msg := err.Error()
144 msg = strings.TrimRight(msg, "\x00")
145 msg = strings.TrimSpace(msg)
146 return msg
147 }
148
149 func sanitizeQuery(q string) string {
150 if q == "" {
151 return ""
152 }
153 q = strings.Join(strings.Fields(q), " ")
154 const limit = 240
155 if len(q) > limit {
156 return q[:limit-3] + "..."
157 }
158 return q
159 }
160
161 func (c *Collector) logQueryErrorOnce(key, query string, err error) {
162 msg := trimDriverMessage(err)
163 if query != "" {
164 query = sanitizeQuery(query)
165 c.logErrorOnce(key, "query failed (%s): %s", query, msg)
166 return
167 }
168 c.logErrorOnce(key, "%s", msg)
169 }
170
171 func (c *Collector) collectSystemInfo(ctx context.Context) {
172 c.systemName = "Unknown"
173
174 _ = c.collectSingleMetric(ctx, "serial_number", querySerialNumber, func(value string) {
175 c.serialNumber = strings.TrimSpace(value)
176 c.Debugf("detected serial number: %s", c.serialNumber)
177 })
178
179 _ = c.collectSingleMetric(ctx, "system_name_metric", querySystemName, func(value string) {
180 name := strings.TrimSpace(value)
181 if name != "" {
182 c.systemName = name
183 c.Debugf("detected system name: %s", c.systemName)
184 }
185 })
186
187 _ = c.collectSingleMetric(ctx, "system_model", querySystemModel, func(value string) {
188 c.model = strings.TrimSpace(value)
189 c.Debugf("detected system model: %s", c.model)
190 })
191
192 err := c.doQuery(ctx, "technology_refresh_level", queryTechnologyRefresh, func(column, value string, lineEnd bool) {
193 if column == "TR_LEVEL" && value != "" {
194 trLevel := strings.TrimSpace(value)
195 if trLevel != "" {
196 c.technologyRefresh = fmt.Sprintf("TR%s", trLevel)
197 c.Debugf("detected Technology Refresh level: %s", c.technologyRefresh)
198 }
199 }
200 })
201 if err != nil {
202 c.Debugf("failed to detect Technology Refresh level: %v", err)
203 }
204
205 if c.systemName == "" {
206 c.systemName = "Unknown"
207 }
208 if c.serialNumber == "" {
209 c.serialNumber = "Unknown"
210 }
211 if c.model == "" {
212 c.model = "Unknown"
213 }
214 if c.osVersion == "" {
215 c.osVersion = "Unknown"
216 }
217 if c.technologyRefresh == "" {
218 c.technologyRefresh = "Unknown"
219 }
220 }
221
222 func (c *Collector) parseIBMiVersion() {
223 if c.osVersion == "" || c.osVersion == "Unknown" {
224 return
225 }
226
227 versionStr := strings.ToUpper(strings.TrimSpace(c.osVersion))
228 if idx := strings.IndexAny(versionStr, " L"); idx > 0 {
229 versionStr = versionStr[:idx]
230 }
231 versionStr = strings.ReplaceAll(versionStr, " ", "")
232
233 if strings.HasPrefix(versionStr, "V") {
234 versionStr = versionStr[1:]
235 if idx := strings.Index(versionStr, "R"); idx > 0 {
236 if major, err := strconv.Atoi(versionStr[:idx]); err == nil {
237 c.versionMajor = major
238 }
239 remainder := versionStr[idx+1:]
240 if mIdx := strings.Index(remainder, "M"); mIdx > 0 {
241 if release, err := strconv.Atoi(remainder[:mIdx]); err == nil {
242 c.versionRelease = release
243 }
244 modStr := remainder[mIdx+1:]
245 modNum := ""
246 for _, ch := range modStr {
247 if ch >= '0' && ch <= '9' {
248 modNum += string(ch)
249 } else {
250 break
251 }
252 }
253 if modNum != "" {
254 if mod, err := strconv.Atoi(modNum); err == nil {
255 c.versionMod = mod
256 }
257 }
258 } else {
259 if release, err := strconv.Atoi(remainder); err == nil {
260 c.versionRelease = release
261 }
262 }
263 }
264 } else {
265 parts := strings.Split(versionStr, ".")
266 if len(parts) >= 2 {
267 if major, err := strconv.Atoi(parts[0]); err == nil {
268 c.versionMajor = major
269 }
270 if release, err := strconv.Atoi(parts[1]); err == nil {
271 c.versionRelease = release
272 }
273 if len(parts) >= 3 {
274 if mod, err := strconv.Atoi(parts[2]); err == nil {
275 c.versionMod = mod
276 }
277 }
278 } else if len(parts) == 1 {
279 if major, err := strconv.Atoi(parts[0]); err == nil {
280 c.versionMajor = major
281 }
282 }
283 }
284
285 c.Debugf("parsed IBM i version: major=%d, release=%d, mod=%d", c.versionMajor, c.versionRelease, c.versionMod)
286 }
287
288 func (c *Collector) logVersionInformation() {
289 if c.versionMajor > 0 {
290 c.Infof("IBM i %d.%d detected - collector will attempt all configured features with graceful error handling", c.versionMajor, c.versionRelease)
291
292 if c.versionMajor >= 7 {
293 if c.versionRelease >= 5 {
294 c.Infof("IBM i 7.5+ typically supports all collector features")
295 } else if c.versionRelease >= 3 {
296 c.Infof("IBM i 7.3+ typically supports ACTIVE_JOB_INFO and IFS_OBJECT_STATISTICS")
297 } else if c.versionRelease >= 2 {
298 c.Infof("IBM i 7.2+ typically supports MESSAGE_QUEUE_INFO and JOB_QUEUE_ENTRIES")
299 } else {
300 c.Infof("IBM i 7.1+ has basic SQL services - some advanced features may not be available")
301 }
302 } else {
303 c.Infof("IBM i %d.x has limited SQL services - many features may not be available", c.versionMajor)
304 }
305
306 c.Infof("Note: admin configuration takes precedence - all enabled features will be attempted regardless of version")
307 } else {
308 c.Infof("IBM i version unknown - collector will attempt all configured features with graceful error handling")
309 }
310 }
311
312 func (c *Collector) setConfigurationDefaults() {
313 c.CollectDiskMetrics = c.CollectDiskMetrics.WithDefault(true)
314 c.CollectSubsystemMetrics = c.CollectSubsystemMetrics.WithDefault(true)
315 c.CollectHTTPServerMetrics = c.CollectHTTPServerMetrics.WithDefault(true)
316 c.CollectPlanCacheMetrics = c.CollectPlanCacheMetrics.WithDefault(true)
317
318 if len(c.ActiveJobs) == 0 {
319 c.CollectActiveJobs = c.CollectActiveJobs.WithDefault(false)
320 } else {
321 c.CollectActiveJobs = c.CollectActiveJobs.WithDefault(true)
322 }
323
324 if c.MessageQueues == nil {
325 c.MessageQueues = append([]string{}, "QSYS/QSYSOPR", "QSYS/QSYSMSG", "QSYS/QHST")
326 }
327
328 c.Infof("Configuration after defaults: DiskMetrics=%t, SubsystemMetrics=%t, ActiveJobs=%t (configured=%d), HTTPServer=%t, PlanCache=%t",
329 c.CollectDiskMetrics.IsEnabled(),
330 c.CollectSubsystemMetrics.IsEnabled(),
331 c.CollectActiveJobs.IsEnabled(),
332 len(c.ActiveJobs),
333 c.CollectHTTPServerMetrics.IsEnabled(),
334 c.CollectPlanCacheMetrics.IsEnabled())
335 }
336
337 func (c *Collector) systemStatusQuery() string {
338 if c.ResetStatistics {
339 return querySystemStatusReset
340 }
341 return querySystemStatusNoReset
342 }
343
344 func (c *Collector) memoryPoolQuery() string {
345 if c.ResetStatistics {
346 return queryMemoryPoolsReset
347 }
348 return queryMemoryPoolsNoReset
349 }
350
351 func (c *Collector) systemActivityQuery() string {
352 if c.ResetStatistics {
353 return querySystemActivityReset
354 }
355 return querySystemActivityNoReset
356 }
357
358 func (c *Collector) supportsMessageQueueTableFunction() bool {
359 if c.versionMajor == 0 {
360 return false
361 }
362 if c.versionMajor > 7 {
363 return true
364 }
365 return c.versionMajor == 7 && c.versionRelease >= 4
366 }