master
go 873 lines 23.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build cgo
4
5 package db2
6
7 import (
8 "context"
9 "fmt"
10 "strconv"
11 "strings"
12 )
13
14 func (c *Collector) collectDatabaseInstances(ctx context.Context) error {
15 // Handle MaxDatabases == 0 (collect all)
16 if c.MaxDatabases == 0 {
17 return c.doCollectDatabaseInstances(ctx, false, nil)
18 }
19
20 // Handle MaxDatabases == -1 (always filter)
21 if c.MaxDatabases == -1 {
22 c.databaseFilterMode = true // Force filter mode
23 return c.doCollectDatabaseInstances(ctx, true, nil)
24 }
25
26 // Handle MaxDatabases > 0 (dynamic threshold)
27 if c.databaseFilterMode {
28 // Already in filter mode, apply selector
29 return c.doCollectDatabaseInstances(ctx, true, nil)
30 }
31
32 // Not in filter mode yet, try to collect all and check count
33 collectedCount := 0
34 err := c.doCollectDatabaseInstances(ctx, false, &collectedCount)
35 if err != nil {
36 return err
37 }
38
39 if collectedCount > c.MaxDatabases {
40 c.databaseFilterMode = true // Exceeded limit, switch to filter mode
41 c.Warningf("Number of databases (%d) exceeded MaxDatabases (%d). Switching to filter mode. Only databases matching '%s' will be collected.", collectedCount, c.MaxDatabases, c.CollectDatabasesMatching)
42 // Re-collect with filtering applied for this cycle
43 return c.doCollectDatabaseInstances(ctx, true, nil)
44 }
45
46 return nil // Already collected in the check phase
47 }
48
49 // Helper function to encapsulate the actual database instance collection logic
50 func (c *Collector) doCollectDatabaseInstances(ctx context.Context, applySelector bool, collectedCount *int) error {
51 // Reset metrics for this collection pass
52 c.mx.databases = make(map[string]databaseInstanceMetrics)
53
54 var currentDB string
55 var currentMetrics databaseInstanceMetrics
56
57 // Reset collectedCount if provided
58 if collectedCount != nil {
59 *collectedCount = 0
60 }
61
62 err := c.doQuery(ctx, queryDatabaseInstances, func(column, value string, lineEnd bool) {
63 switch column {
64 case "DB_NAME":
65 // Save previous database metrics if we have any
66 if currentDB != "" {
67 c.mx.databases[currentDB] = currentMetrics
68 }
69
70 dbName := strings.TrimSpace(value)
71 if dbName == "" {
72 currentDB = ""
73 return
74 }
75
76 // Apply selector if applySelector is true AND selector is configured
77 if applySelector && c.databaseSelector != nil && !c.databaseSelector.MatchString(dbName) {
78 currentDB = ""
79 return // Skip this database
80 }
81
82 // Increment count if we are counting
83 if collectedCount != nil {
84 *collectedCount++
85 }
86
87 currentDB = dbName
88 currentMetrics = databaseInstanceMetrics{}
89
90 if _, exists := c.databases[dbName]; !exists {
91 c.databases[dbName] = &databaseMetrics{name: dbName}
92 }
93
94 case "DB_STATUS":
95 if currentDB != "" {
96 // Map status to numeric value
97 statusValue := int64(0)
98 switch strings.ToUpper(value) {
99 case "ACTIVE":
100 statusValue = 1
101 case "INACTIVE":
102 statusValue = 0
103 default:
104 statusValue = -1
105 }
106
107 c.databases[currentDB].status = value
108 currentMetrics.Status = statusValue
109 }
110
111 case "APPLS_CUR_CONS":
112 if currentDB != "" {
113 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
114 c.databases[currentDB].applications = v
115 currentMetrics.Applications = v
116 }
117 }
118 }
119
120 // At end of row, save the metrics
121 if lineEnd && currentDB != "" {
122 c.mx.databases[currentDB] = currentMetrics
123 currentDB = ""
124 }
125 })
126
127 // Save last database if query ended without lineEnd
128 if currentDB != "" {
129 c.mx.databases[currentDB] = currentMetrics
130 }
131
132 // Count active and inactive databases
133 for dbName, db := range c.databases {
134 if _, exists := c.mx.databases[dbName]; exists {
135 // Database was collected this cycle
136 if strings.ToUpper(db.status) == "ACTIVE" {
137 c.mx.DatabaseCountActive++
138 } else {
139 c.mx.DatabaseCountInactive++
140 }
141 }
142 }
143
144 return err
145 }
146
147 func (c *Collector) collectBufferpoolInstances(ctx context.Context) error {
148 query := queryMonGetBufferpool
149 c.Debugf("using MON_GET_BUFFERPOOL for bufferpool instances")
150
151 var currentBP string
152 err := c.doQuery(ctx, query, func(column, value string, lineEnd bool) {
153 switch column {
154 case "BP_NAME":
155 currentBP = strings.TrimSpace(value)
156 if currentBP == "" {
157 return
158 }
159
160 if !c.allowBufferpool(currentBP) {
161 currentBP = ""
162 return
163 }
164
165 if _, exists := c.bufferpools[currentBP]; !exists {
166 c.bufferpools[currentBP] = &bufferpoolMetrics{name: currentBP}
167 }
168 if _, exists := c.mx.bufferpools[currentBP]; !exists {
169 c.mx.bufferpools[currentBP] = bufferpoolInstanceMetrics{}
170 }
171
172 case "PAGESIZE":
173 if currentBP != "" {
174 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
175 c.bufferpools[currentBP].pageSize = v
176 metrics := c.mx.bufferpools[currentBP]
177 metrics.PageSize = v
178 c.mx.bufferpools[currentBP] = metrics
179 }
180 }
181
182 case "TOTAL_PAGES":
183 if currentBP != "" {
184 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
185 metrics := c.mx.bufferpools[currentBP]
186 metrics.TotalPages = v
187 c.mx.bufferpools[currentBP] = metrics
188 }
189 }
190
191 case "USED_PAGES":
192 if currentBP != "" {
193 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
194 metrics := c.mx.bufferpools[currentBP]
195 metrics.UsedPages = v
196 c.mx.bufferpools[currentBP] = metrics
197 }
198 }
199
200 case "DATA_PAGES_FOUND":
201 if currentBP != "" {
202 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
203 metrics := c.mx.bufferpools[currentBP]
204 metrics.DataHits = v
205 c.mx.bufferpools[currentBP] = metrics
206 }
207 }
208
209 case "INDEX_PAGES_FOUND":
210 if currentBP != "" {
211 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
212 metrics := c.mx.bufferpools[currentBP]
213 metrics.IndexHits = v
214 c.mx.bufferpools[currentBP] = metrics
215 }
216 }
217
218 case "XDA_PAGES_FOUND":
219 if currentBP != "" {
220 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
221 metrics := c.mx.bufferpools[currentBP]
222 metrics.XDAHits = v
223 c.mx.bufferpools[currentBP] = metrics
224 }
225 }
226
227 case "COL_PAGES_FOUND":
228 if currentBP != "" {
229 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
230 metrics := c.mx.bufferpools[currentBP]
231 metrics.ColumnHits = v
232 c.mx.bufferpools[currentBP] = metrics
233 }
234 }
235
236 case "LOGICAL_READS":
237 if currentBP != "" {
238 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
239 metrics := c.mx.bufferpools[currentBP]
240 metrics.LogicalReads = v
241 c.mx.bufferpools[currentBP] = metrics
242 }
243 }
244
245 case "PHYSICAL_READS":
246 if currentBP != "" {
247 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
248 metrics := c.mx.bufferpools[currentBP]
249 metrics.PhysicalReads = v
250 c.mx.bufferpools[currentBP] = metrics
251 }
252 }
253
254 case "TOTAL_READS":
255 if currentBP != "" {
256 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
257 metrics := c.mx.bufferpools[currentBP]
258 metrics.TotalReads = v
259 c.mx.bufferpools[currentBP] = metrics
260 }
261 }
262
263 case "DATA_LOGICAL_READS":
264 if currentBP != "" {
265 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
266 metrics := c.mx.bufferpools[currentBP]
267 metrics.DataLogicalReads = v
268 // Calculate data misses
269 metrics.DataMisses = v - metrics.DataHits
270 c.mx.bufferpools[currentBP] = metrics
271 }
272 }
273
274 case "DATA_PHYSICAL_READS":
275 if currentBP != "" {
276 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
277 metrics := c.mx.bufferpools[currentBP]
278 metrics.DataPhysicalReads = v
279 c.mx.bufferpools[currentBP] = metrics
280 }
281 }
282
283 case "INDEX_LOGICAL_READS":
284 if currentBP != "" {
285 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
286 metrics := c.mx.bufferpools[currentBP]
287 metrics.IndexLogicalReads = v
288 // Calculate index misses
289 metrics.IndexMisses = v - metrics.IndexHits
290 c.mx.bufferpools[currentBP] = metrics
291 }
292 }
293
294 case "INDEX_PHYSICAL_READS":
295 if currentBP != "" {
296 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
297 metrics := c.mx.bufferpools[currentBP]
298 metrics.IndexPhysicalReads = v
299 c.mx.bufferpools[currentBP] = metrics
300 }
301 }
302
303 case "XDA_LOGICAL_READS":
304 if currentBP != "" {
305 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
306 metrics := c.mx.bufferpools[currentBP]
307 metrics.XDALogicalReads = v
308 // Calculate XDA misses
309 metrics.XDAMisses = v - metrics.XDAHits
310 c.mx.bufferpools[currentBP] = metrics
311 }
312 }
313
314 case "XDA_PHYSICAL_READS":
315 if currentBP != "" {
316 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
317 metrics := c.mx.bufferpools[currentBP]
318 metrics.XDAPhysicalReads = v
319 c.mx.bufferpools[currentBP] = metrics
320 }
321 }
322
323 case "COLUMN_LOGICAL_READS":
324 if currentBP != "" {
325 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
326 metrics := c.mx.bufferpools[currentBP]
327 metrics.ColumnLogicalReads = v
328 // Calculate column misses
329 metrics.ColumnMisses = v - metrics.ColumnHits
330 c.mx.bufferpools[currentBP] = metrics
331 }
332 }
333
334 case "COLUMN_PHYSICAL_READS":
335 if currentBP != "" {
336 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
337 metrics := c.mx.bufferpools[currentBP]
338 metrics.ColumnPhysicalReads = v
339 c.mx.bufferpools[currentBP] = metrics
340 }
341 }
342
343 case "WRITES":
344 if currentBP != "" {
345 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
346 metrics := c.mx.bufferpools[currentBP]
347 metrics.Writes = v
348 c.mx.bufferpools[currentBP] = metrics
349 }
350 }
351
352 // MON_GET specific fields for buffer pool instances
353 case "POOL_CUR_SIZE":
354 if currentBP != "" {
355 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
356 // For MON_GET, this is already in pages
357 metrics := c.mx.bufferpools[currentBP]
358 metrics.TotalPages = v
359 c.mx.bufferpools[currentBP] = metrics
360 }
361 }
362
363 case "POOL_WATERMARK":
364 if currentBP != "" {
365 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
366 // Watermark can be used as an indicator of usage
367 metrics := c.mx.bufferpools[currentBP]
368 metrics.UsedPages = v // Approximation for MON_GET
369 c.mx.bufferpools[currentBP] = metrics
370 }
371 }
372 }
373
374 // Calculate hit/miss metrics at end of line
375 if lineEnd && currentBP != "" {
376 metrics := c.mx.bufferpools[currentBP]
377
378 // For MON_GET queries, we calculate hits from logical - physical
379 // Calculate hits for each type (hits = logical - physical)
380 metrics.DataHits = metrics.DataLogicalReads - metrics.DataPhysicalReads
381 metrics.DataMisses = metrics.DataPhysicalReads
382
383 metrics.IndexHits = metrics.IndexLogicalReads - metrics.IndexPhysicalReads
384 metrics.IndexMisses = metrics.IndexPhysicalReads
385
386 metrics.XDAHits = metrics.XDALogicalReads - metrics.XDAPhysicalReads
387 metrics.XDAMisses = metrics.XDAPhysicalReads
388
389 metrics.ColumnHits = metrics.ColumnLogicalReads - metrics.ColumnPhysicalReads
390 metrics.ColumnMisses = metrics.ColumnPhysicalReads
391
392 // Ensure hits are not negative (shouldn't happen with MON_GET approach but safety check)
393 if metrics.DataHits < 0 {
394 metrics.DataHits = 0
395 metrics.DataMisses = metrics.DataLogicalReads
396 }
397 if metrics.IndexHits < 0 {
398 metrics.IndexHits = 0
399 metrics.IndexMisses = metrics.IndexLogicalReads
400 }
401 if metrics.XDAHits < 0 {
402 metrics.XDAHits = 0
403 metrics.XDAMisses = metrics.XDALogicalReads
404 }
405 if metrics.ColumnHits < 0 {
406 metrics.ColumnHits = 0
407 metrics.ColumnMisses = metrics.ColumnLogicalReads
408 }
409
410 // Calculate overall hits and misses
411 metrics.Hits = metrics.DataHits + metrics.IndexHits + metrics.XDAHits + metrics.ColumnHits
412 metrics.Misses = metrics.DataMisses + metrics.IndexMisses + metrics.XDAMisses + metrics.ColumnMisses
413
414 // Calculate total reads
415 totalLogical := metrics.DataLogicalReads + metrics.IndexLogicalReads + metrics.XDALogicalReads + metrics.ColumnLogicalReads
416 totalPhysical := metrics.DataPhysicalReads + metrics.IndexPhysicalReads + metrics.XDAPhysicalReads + metrics.ColumnPhysicalReads
417 metrics.LogicalReads = totalLogical
418 metrics.PhysicalReads = totalPhysical
419 metrics.TotalReads = totalLogical + totalPhysical
420
421 c.mx.bufferpools[currentBP] = metrics
422 }
423 })
424
425 return err
426 }
427
428 func (c *Collector) collectTablespaceInstances(ctx context.Context) error {
429 if c.MaxTablespaces <= 0 {
430 return nil
431 }
432
433 // Always use MON_GET_TABLESPACE for tablespace metrics
434 query := queryMonGetTablespace
435 c.Debugf("using MON_GET_TABLESPACE for tablespace metrics")
436
437 var currentTbsp string
438 err := c.doQuery(ctx, query, func(column, value string, lineEnd bool) {
439 switch column {
440 case "TBSP_NAME":
441 currentTbsp = strings.TrimSpace(value)
442 if currentTbsp == "" {
443 return
444 }
445
446 if !c.allowTablespace(currentTbsp, "", "") {
447 currentTbsp = ""
448 return
449 }
450
451 if _, exists := c.tablespaces[currentTbsp]; !exists {
452 c.tablespaces[currentTbsp] = &tablespaceMetrics{name: currentTbsp}
453 }
454 c.mx.tablespaces[currentTbsp] = tablespaceInstanceMetrics{}
455
456 case "TBSP_TYPE":
457 if currentTbsp != "" {
458 c.tablespaces[currentTbsp].tbspType = value
459 }
460
461 case "TBSP_CONTENT_TYPE":
462 if currentTbsp != "" {
463 c.tablespaces[currentTbsp].contentType = value
464 }
465
466 case "TBSP_STATE":
467 if currentTbsp != "" {
468 c.tablespaces[currentTbsp].state = value
469 // Map state to numeric
470 stateValue := int64(0)
471 switch strings.ToUpper(value) {
472 case "NORMAL":
473 stateValue = 1
474 case "OFFLINE":
475 stateValue = 0
476 default:
477 stateValue = -1
478 }
479 metrics := c.mx.tablespaces[currentTbsp]
480 metrics.State = stateValue
481 c.mx.tablespaces[currentTbsp] = metrics
482 }
483
484 case "TOTAL_SIZE":
485 if currentTbsp != "" {
486 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
487 metrics := c.mx.tablespaces[currentTbsp]
488 metrics.TotalSize = v
489 c.mx.tablespaces[currentTbsp] = metrics
490 }
491 }
492
493 case "USED_SIZE":
494 if currentTbsp != "" {
495 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
496 metrics := c.mx.tablespaces[currentTbsp]
497 metrics.UsedSize = v
498 c.mx.tablespaces[currentTbsp] = metrics
499 }
500 }
501
502 case "FREE_SIZE":
503 if currentTbsp != "" {
504 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
505 metrics := c.mx.tablespaces[currentTbsp]
506 metrics.FreeSize = v
507 c.mx.tablespaces[currentTbsp] = metrics
508 }
509 }
510
511 case "USABLE_SIZE":
512 if currentTbsp != "" {
513 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
514 metrics := c.mx.tablespaces[currentTbsp]
515 metrics.UsableSize = v
516 c.mx.tablespaces[currentTbsp] = metrics
517 }
518 }
519
520 case "USED_PERCENT":
521 if currentTbsp != "" {
522 if v, err := strconv.ParseFloat(value, 64); err == nil {
523 metrics := c.mx.tablespaces[currentTbsp]
524 metrics.UsedPercent = int64(v * Precision)
525 c.mx.tablespaces[currentTbsp] = metrics
526 }
527 }
528
529 case "TBSP_PAGE_SIZE":
530 if currentTbsp != "" {
531 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
532 metrics := c.mx.tablespaces[currentTbsp]
533 metrics.PageSize = v
534 c.mx.tablespaces[currentTbsp] = metrics
535 }
536 }
537 }
538
539 if currentTbsp != "" {
540 meta := c.tablespaces[currentTbsp]
541 if !c.allowTablespace(currentTbsp, meta.contentType, meta.state) {
542 delete(c.tablespaces, currentTbsp)
543 delete(c.mx.tablespaces, currentTbsp)
544 currentTbsp = ""
545 }
546 }
547
548 if lineEnd {
549 currentTbsp = ""
550 }
551 })
552
553 return err
554 }
555
556 func (c *Collector) collectConnectionInstances(ctx context.Context) error {
557 // MaxConnections <=0 disables per-connection collection entirely.
558 if c.MaxConnections <= 0 {
559 return nil
560 }
561
562 query := queryMonGetConnectionDetails
563 c.Debugf("using MON_GET_CONNECTION for connection instances")
564
565 var currentAppID string
566 err := c.doQuery(ctx, query, func(column, value string, lineEnd bool) {
567 switch column {
568 case "APPLICATION_ID":
569 currentAppID = strings.TrimSpace(value)
570 if currentAppID == "" {
571 return
572 }
573
574 if _, exists := c.connections[currentAppID]; !exists {
575 c.connections[currentAppID] = &connectionMetrics{applicationID: currentAppID}
576 }
577
578 c.mx.connections[currentAppID] = connectionInstanceMetrics{}
579
580 case "APPLICATION_NAME":
581 if currentAppID != "" {
582 c.connections[currentAppID].applicationName = value
583 }
584
585 case "CLIENT_HOSTNAME":
586 if currentAppID != "" {
587 c.connections[currentAppID].clientHostname = value
588 }
589
590 case "CLIENT_IPADDR":
591 if currentAppID != "" {
592 c.connections[currentAppID].clientIP = value
593 }
594
595 case "SESSION_AUTH_ID":
596 if currentAppID != "" {
597 c.connections[currentAppID].clientUser = value
598 }
599
600 case "APPL_STATUS":
601 if currentAppID != "" {
602 c.connections[currentAppID].connectionState = value
603 stateValue := int64(0)
604 execQueries := int64(0)
605 switch strings.ToUpper(value) {
606 case "CONNECTED":
607 stateValue = 1
608 case "UOWEXEC":
609 stateValue = 2
610 execQueries = 1
611 }
612 metrics := c.mx.connections[currentAppID]
613 metrics.State = stateValue
614 metrics.ExecutingQueries = execQueries
615 c.mx.connections[currentAppID] = metrics
616 }
617
618 case "ROWS_READ":
619 if currentAppID != "" {
620 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
621 metrics := c.mx.connections[currentAppID]
622 metrics.RowsRead = v
623 c.mx.connections[currentAppID] = metrics
624 }
625 }
626
627 case "ROWS_WRITTEN":
628 if currentAppID != "" {
629 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
630 metrics := c.mx.connections[currentAppID]
631 metrics.RowsWritten = v
632 c.mx.connections[currentAppID] = metrics
633 }
634 }
635
636 case "TOTAL_CPU_TIME":
637 if currentAppID != "" {
638 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
639 metrics := c.mx.connections[currentAppID]
640 metrics.TotalCPUTime = v
641 c.mx.connections[currentAppID] = metrics
642 }
643 }
644 }
645
646 if lineEnd && currentAppID != "" {
647 meta := c.connections[currentAppID]
648 if !c.allowConnection(currentAppID, meta) {
649 delete(c.connections, currentAppID)
650 delete(c.mx.connections, currentAppID)
651 }
652 currentAppID = ""
653 }
654
655 })
656
657 return err
658 }
659
660 // Screen 26: Instance Memory Sets collection using MON_GET_MEMORY_SET
661 func (c *Collector) collectMemorySetInstances(ctx context.Context) error {
662 // Always use MON_GET_MEMORY_SET for memory set metrics
663 query := queryMonGetMemorySet
664
665 c.Debugf("collecting memory set instances using MON_GET_MEMORY_SET")
666
667 // Track seen memory sets for lifecycle management
668 seen := make(map[string]bool)
669
670 err := c.doQuery(ctx, query, func(column, value string, lineEnd bool) {
671 if column == "HOST_NAME" {
672 c.currentMemorySetHostName = value
673 return
674 }
675
676 if c.currentMemorySetHostName == "" {
677 return // Skip until we have a host name
678 }
679
680 switch column {
681 case "DB_NAME":
682 c.currentMemorySetDBName = value
683 case "MEMORY_SET_TYPE":
684 c.currentMemorySetType = value
685 case "MEMBER":
686 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
687 c.currentMemorySetMember = v
688 }
689 }
690
691 // Process metrics when we have complete record
692 if lineEnd && c.currentMemorySetHostName != "" && c.currentMemorySetDBName != "" {
693 // Use DATABASE as default type if not set (ODBC panic might prevent collection)
694 if c.currentMemorySetType == "" {
695 c.currentMemorySetType = "DATABASE"
696 }
697
698 // Create unique identifier for this memory set
699 setKey := fmt.Sprintf("%s.%s.%s.%d",
700 c.currentMemorySetHostName,
701 c.currentMemorySetDBName,
702 c.currentMemorySetType,
703 c.currentMemorySetMember)
704
705 seen[setKey] = true
706
707 // Initialize memory set if not seen before
708 if _, exists := c.memorySets[setKey]; !exists {
709 c.memorySets[setKey] = &memorySetInstanceMetrics{
710 hostName: c.currentMemorySetHostName,
711 dbName: c.currentMemorySetDBName,
712 setType: c.currentMemorySetType,
713 member: c.currentMemorySetMember,
714 }
715 c.Debugf("created memory set instance: %s (type: %s)", setKey, c.currentMemorySetType)
716 }
717
718 // Reset for next record
719 c.currentMemorySetHostName = ""
720 c.currentMemorySetDBName = ""
721 c.currentMemorySetType = ""
722 c.currentMemorySetMember = 0
723 }
724
725 // Process individual metrics
726 if c.currentMemorySetHostName != "" && c.currentMemorySetDBName != "" {
727 // Use DATABASE as default type if not set
728 memSetType := c.currentMemorySetType
729 if memSetType == "" {
730 memSetType = "DATABASE"
731 }
732
733 setKey := fmt.Sprintf("%s.%s.%s.%d",
734 c.currentMemorySetHostName,
735 c.currentMemorySetDBName,
736 memSetType,
737 c.currentMemorySetMember)
738
739 // Initialize memory set if it doesn't exist
740 if _, exists := c.memorySets[setKey]; !exists {
741 c.memorySets[setKey] = &memorySetInstanceMetrics{
742 hostName: c.currentMemorySetHostName,
743 dbName: c.currentMemorySetDBName,
744 setType: memSetType,
745 member: c.currentMemorySetMember,
746 }
747 }
748
749 // Update the metrics
750 ms := c.memorySets[setKey]
751 switch column {
752 case "MEMORY_SET_USED":
753 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
754 ms.Used = v
755 }
756 case "MEMORY_SET_COMMITTED":
757 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
758 ms.Committed = v
759 }
760 case "MEMORY_SET_USED_HWM":
761 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
762 ms.HighWaterMark = v
763 }
764 case "ADDITIONAL_COMMITTED":
765 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
766 ms.AdditionalCommitted = v
767 }
768 case "PERCENT_USED_HWM":
769 if v, err := strconv.ParseFloat(value, 64); err == nil {
770 ms.PercentUsedHWM = int64(v * Precision)
771 }
772 }
773 }
774 })
775
776 // Remove stale memory sets
777 for setKey := range c.memorySets {
778 if !seen[setKey] {
779 delete(c.memorySets, setKey)
780 }
781 }
782
783 if err != nil {
784 return fmt.Errorf("failed to collect memory set instances: %w", err)
785 }
786
787 c.Debugf("collected %d memory set instances", len(seen))
788 for k := range seen {
789 c.Debugf(" memory set: %s", k)
790 }
791 return nil
792 }
793
794 func (c *Collector) collectPrefetcherInstances(ctx context.Context) error {
795 // Always use MON_GET functions for prefetcher metrics
796 // Track seen prefetcher instances
797 seen := make(map[string]bool)
798 c.currentBufferPoolName = ""
799
800 err := c.doQuery(ctx, queryPrefetcherMetrics, func(column, value string, lineEnd bool) {
801 switch column {
802 case "BUFFERPOOL_NAME":
803 bufferPoolName := strings.TrimSpace(value)
804 if bufferPoolName == "" {
805 return
806 }
807
808 c.currentBufferPoolName = bufferPoolName
809 seen[bufferPoolName] = true
810
811 // Create prefetcher instance if new
812 if _, exists := c.prefetchers[bufferPoolName]; !exists {
813 c.prefetchers[bufferPoolName] = &prefetcherInstanceMetrics{}
814 }
815
816 case "PREFETCH_RATIO_PCT":
817 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
818 if v, err := strconv.ParseFloat(value, 64); err == nil {
819 c.prefetchers[c.currentBufferPoolName].PrefetchRatio = int64(v * Precision)
820 }
821 }
822
823 case "CLEANER_RATIO_PCT":
824 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
825 if v, err := strconv.ParseFloat(value, 64); err == nil {
826 c.prefetchers[c.currentBufferPoolName].CleanerRatio = int64(v * Precision)
827 }
828 }
829
830 case "PHYSICAL_READS":
831 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
832 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
833 c.prefetchers[c.currentBufferPoolName].PhysicalReads = v
834 }
835 }
836
837 case "ASYNCHRONOUS_READS":
838 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
839 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
840 c.prefetchers[c.currentBufferPoolName].AsyncReads = v
841 }
842 }
843
844 case "PREFETCH_WAITS_TIME_MS":
845 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
846 if v, err := strconv.ParseFloat(value, 64); err == nil {
847 c.prefetchers[c.currentBufferPoolName].AvgWaitTime = int64(v * Precision)
848 }
849 }
850
851 case "UNREAD_PREFETCH_PAGES":
852 if c.currentBufferPoolName != "" && c.prefetchers[c.currentBufferPoolName] != nil {
853 if v, err := strconv.ParseInt(value, 10, 64); err == nil {
854 c.prefetchers[c.currentBufferPoolName].UnreadPages = v
855 }
856 }
857 }
858 })
859
860 // Remove stale prefetcher instances
861 for bufferPoolName := range c.prefetchers {
862 if !seen[bufferPoolName] {
863 delete(c.prefetchers, bufferPoolName)
864 }
865 }
866
867 if err != nil {
868 return fmt.Errorf("failed to collect prefetcher instances: %w", err)
869 }
870
871 c.Debugf("collected %d prefetcher instances", len(seen))
872 return nil
873 }