master
go 428 lines 13.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build cgo && ibm_mq
4
5 package pcf
6
7 import (
8 "fmt"
9 "strings"
10
11 "github.com/ibm-messaging/mq-golang/v5/ibmmq"
12 )
13
14 const (
15 // Maximum number of statistics messages to process per collection
16 // Set to 1M to handle any realistic deployment - we want to process ALL messages
17 maxStatisticsMessages = 1000000
18 )
19
20 // GetStatisticsQueue collects messages from SYSTEM.ADMIN.STATISTICS.QUEUE
21 // This queue contains pre-published statistics messages with extended metrics
22 // like min/max depth, average queue time, operation counts, etc.
23 //
24 // LIMITATION: The IBM MQ Go library (github.com/ibm-messaging/mq-golang/v5) currently
25 // does not support reading messages from queues - it only supports PCF administrative
26 // commands. This functionality requires MQGET operations which are not yet implemented
27 // in the Go library. For now, this returns empty results to maintain compatibility.
28 func (c *Client) GetStatisticsQueue() (*StatisticsCollectionResult, error) {
29 if !c.connected {
30 c.protocol.Debugf("GetStatisticsQueue FAILED: not connected")
31 return nil, fmt.Errorf("not connected")
32 }
33
34 c.warnOnce("statistics_queue_unimplemented", "Statistics queue collection is not yet implemented in the IBM MQ Go library migration")
35 c.protocol.Debugf("GetStatisticsQueue returning empty results - feature needs implementation")
36
37 // Return empty successful result for now
38 result := &StatisticsCollectionResult{
39 Stats: CollectionStats{
40 Discovery: struct {
41 Success bool
42 AvailableItems int64
43 InvisibleItems int64
44 IncludedItems int64
45 ExcludedItems int64
46 UnparsedItems int64
47 ErrorCounts map[int32]int
48 }{
49 Success: true,
50 AvailableItems: 0,
51 InvisibleItems: 0,
52 IncludedItems: 0,
53 ExcludedItems: 0,
54 UnparsedItems: 0,
55 ErrorCounts: make(map[int32]int),
56 },
57 },
58 Messages: []StatisticsMessage{},
59 }
60
61 c.protocol.Debugf("GetStatisticsQueue SUCCESS (stub implementation)")
62 return result, nil
63 }
64
65 // parseStatisticsMessage parses a raw statistics message into structured data
66 // LIMITATION: Cannot be implemented until IBM MQ Go library supports MQGET operations
67 func (c *Client) parseStatisticsMessage(buffer []byte, md *ibmmq.MQMD) (*StatisticsMessage, error) {
68 return nil, fmt.Errorf("statistics message parsing not yet implemented")
69 }
70
71 // shouldSkipOldStatisticsMessage checks if a statistics message should be skipped
72 // LIMITATION: Cannot be implemented until IBM MQ Go library supports MQGET operations
73 func (c *Client) shouldSkipOldStatisticsMessage(msg *StatisticsMessage) bool {
74 return false
75 }
76
77 // parseQueueStatistics parses queue statistics from a PCF message
78 // NOTE: This function is partially implemented for when MQGET operations become available
79 func (c *Client) parseQueueStatistics(buffer []byte) ([]QueueStatistics, error) {
80 attrs, err := c.parsePCFResponse(buffer, "STATISTICS_Q")
81 if err != nil {
82 return nil, fmt.Errorf("failed to parse PCF attributes: %w", err)
83 }
84
85 var stats []QueueStatistics
86
87 // Extract queue name
88 queueName, ok := attrs[ibmmq.MQCA_Q_NAME].(string)
89 if !ok {
90 return nil, fmt.Errorf("queue name not found in statistics message")
91 }
92
93 var stat QueueStatistics
94 stat.Name = queueName
95
96 // Extract queue type if available
97 if qType, ok := attrs[ibmmq.MQIA_Q_TYPE]; ok {
98 if typeVal, ok := qType.(int32); ok {
99 stat.Type = QueueType(typeVal)
100 }
101 }
102
103 // Extract min/max depth
104 if val, ok := attrs[ibmmq.MQIAMO_Q_MIN_DEPTH]; ok {
105 if intVal, ok := val.(int32); ok {
106 stat.MinDepth = AttributeValue(intVal)
107 }
108 }
109 if val, ok := attrs[ibmmq.MQIAMO_Q_MAX_DEPTH]; ok {
110 if intVal, ok := val.(int32); ok {
111 stat.MaxDepth = AttributeValue(intVal)
112 }
113 }
114
115 // Extract average queue time (array with [0]=non-persistent, [1]=persistent)
116 if val, ok := attrs[ibmmq.MQIAMO64_AVG_Q_TIME]; ok {
117 if arrayVal, ok := val.([]int64); ok && len(arrayVal) >= 2 {
118 stat.AvgQTimeNonPersistent = AttributeValue(arrayVal[0])
119 stat.AvgQTimePersistent = AttributeValue(arrayVal[1])
120 }
121 }
122
123 // Extract short/long time indicators
124 // Note: These constants appear to be MQIAMO_Q_TIME_AVG/MIN/MAX in some versions
125 // For now, we'll leave these fields unpopulated until we can verify the correct constants
126 stat.QTimeShort = NotCollected
127 stat.QTimeLong = NotCollected
128
129 // Extract put/get operations (arrays with [0]=non-persistent, [1]=persistent)
130 if val, ok := attrs[ibmmq.MQIAMO_PUTS]; ok {
131 if arrayVal, ok := val.([]int32); ok && len(arrayVal) >= 2 {
132 stat.PutsNonPersistent = AttributeValue(arrayVal[0])
133 stat.PutsPersistent = AttributeValue(arrayVal[1])
134 }
135 }
136 if val, ok := attrs[ibmmq.MQIAMO_GETS]; ok {
137 if arrayVal, ok := val.([]int32); ok && len(arrayVal) >= 2 {
138 stat.GetsNonPersistent = AttributeValue(arrayVal[0])
139 stat.GetsPersistent = AttributeValue(arrayVal[1])
140 }
141 }
142
143 // Extract byte counters (arrays with [0]=non-persistent, [1]=persistent)
144 if val, ok := attrs[ibmmq.MQIAMO64_PUT_BYTES]; ok {
145 if arrayVal, ok := val.([]int64); ok && len(arrayVal) >= 2 {
146 stat.PutBytesNonPersistent = AttributeValue(arrayVal[0])
147 stat.PutBytesPersistent = AttributeValue(arrayVal[1])
148 }
149 }
150 if val, ok := attrs[ibmmq.MQIAMO64_GET_BYTES]; ok {
151 if arrayVal, ok := val.([]int64); ok && len(arrayVal) >= 2 {
152 stat.GetBytesNonPersistent = AttributeValue(arrayVal[0])
153 stat.GetBytesPersistent = AttributeValue(arrayVal[1])
154 }
155 }
156
157 // Extract failure counters
158 if val, ok := attrs[ibmmq.MQIAMO_PUTS_FAILED]; ok {
159 if intVal, ok := val.(int32); ok {
160 stat.PutsFailed = AttributeValue(intVal)
161 }
162 }
163 if val, ok := attrs[ibmmq.MQIAMO_PUT1S_FAILED]; ok {
164 if intVal, ok := val.(int32); ok {
165 stat.Put1sFailed = AttributeValue(intVal)
166 }
167 }
168 if val, ok := attrs[ibmmq.MQIAMO_GETS_FAILED]; ok {
169 if intVal, ok := val.(int32); ok {
170 stat.GetsFailed = AttributeValue(intVal)
171 }
172 }
173 if val, ok := attrs[ibmmq.MQIAMO_BROWSES_FAILED]; ok {
174 if intVal, ok := val.(int32); ok {
175 stat.BrowsesFailed = AttributeValue(intVal)
176 }
177 }
178
179 // Extract message lifecycle counters
180 if val, ok := attrs[ibmmq.MQIAMO_MSGS_EXPIRED]; ok {
181 if intVal, ok := val.(int32); ok {
182 stat.MsgsExpired = AttributeValue(intVal)
183 }
184 }
185 if val, ok := attrs[ibmmq.MQIAMO_MSGS_PURGED]; ok {
186 if intVal, ok := val.(int32); ok {
187 stat.MsgsPurged = AttributeValue(intVal)
188 }
189 }
190 if val, ok := attrs[ibmmq.MQIAMO_MSGS_NOT_QUEUED]; ok {
191 if intVal, ok := val.(int32); ok {
192 stat.MsgsNotQueued = AttributeValue(intVal)
193 }
194 }
195
196 // Extract additional counters
197 if val, ok := attrs[ibmmq.MQIAMO_BROWSES]; ok {
198 if intVal, ok := val.(int32); ok {
199 stat.BrowseCount = AttributeValue(intVal)
200 }
201 }
202 if val, ok := attrs[ibmmq.MQIAMO64_BROWSE_BYTES]; ok {
203 if intVal, ok := val.(int64); ok {
204 stat.BrowseBytes = AttributeValue(intVal)
205 }
206 }
207 if val, ok := attrs[ibmmq.MQIAMO_PUT1S]; ok {
208 if intVal, ok := val.(int32); ok {
209 stat.Put1Count = AttributeValue(intVal)
210 }
211 }
212
213 // Extract timestamp information
214 if val, ok := attrs[ibmmq.MQCAMO_START_DATE]; ok {
215 if dateStr, ok := val.(string); ok {
216 // For now, just mark as collected without conversion
217 // Future: implement proper date string parsing
218 stat.StartDate = AttributeValue(1) // Mark as collected
219 _ = dateStr // Avoid unused variable warning
220 }
221 }
222 if val, ok := attrs[ibmmq.MQCAMO_START_TIME]; ok {
223 if timeStr, ok := val.(string); ok {
224 // For now, just mark as collected without conversion
225 // Future: implement proper time string parsing
226 stat.StartTime = AttributeValue(1) // Mark as collected
227 _ = timeStr // Avoid unused variable warning
228 }
229 }
230
231 stats = append(stats, stat)
232
233 c.protocol.Debugf("Parsed queue statistics for queue '%s' - min_depth: %v, max_depth: %v",
234 stat.Name, stat.MinDepth, stat.MaxDepth)
235
236 return stats, nil
237 }
238
239 // parseChannelStatistics parses channel statistics from a PCF message
240 func (c *Client) parseChannelStatistics(buffer []byte) ([]ChannelStatistics, error) {
241 attrs, err := c.parsePCFResponse(buffer, "STATISTICS_CHANNEL")
242 if err != nil {
243 return nil, fmt.Errorf("failed to parse PCF attributes: %w", err)
244 }
245
246 var stats []ChannelStatistics
247
248 // Extract channel name
249 channelName, ok := attrs[ibmmq.MQCACH_CHANNEL_NAME].(string)
250 if !ok {
251 return nil, fmt.Errorf("channel name not found in statistics message")
252 }
253
254 var stat ChannelStatistics
255 stat.Name = channelName
256
257 // Extract channel type and status if available
258 if cType, ok := attrs[ibmmq.MQIACH_CHANNEL_TYPE]; ok {
259 if typeVal, ok := cType.(int32); ok {
260 stat.Type = ChannelType(typeVal)
261 }
262 }
263 if cStatus, ok := attrs[ibmmq.MQIACH_CHANNEL_STATUS]; ok {
264 if statusVal, ok := cStatus.(int32); ok {
265 stat.Status = ChannelStatus(statusVal)
266 }
267 }
268
269 // Extract message metrics
270 if val, ok := attrs[ibmmq.MQIAMO_MSGS]; ok {
271 if intVal, ok := val.(int32); ok {
272 stat.Messages = AttributeValue(intVal)
273 }
274 }
275 if val, ok := attrs[ibmmq.MQIAMO64_BYTES]; ok {
276 if intVal, ok := val.(int64); ok {
277 stat.Bytes = AttributeValue(intVal)
278 }
279 }
280 if val, ok := attrs[ibmmq.MQIAMO_FULL_BATCHES]; ok {
281 if intVal, ok := val.(int32); ok {
282 stat.FullBatches = AttributeValue(intVal)
283 }
284 }
285 if val, ok := attrs[ibmmq.MQIAMO_INCOMPLETE_BATCHES]; ok {
286 if intVal, ok := val.(int32); ok {
287 stat.IncompleteBatches = AttributeValue(intVal)
288 }
289 }
290 if val, ok := attrs[ibmmq.MQIAMO_AVG_BATCH_SIZE]; ok {
291 if intVal, ok := val.(int32); ok {
292 stat.AvgBatchSize = AttributeValue(intVal)
293 }
294 }
295 if val, ok := attrs[ibmmq.MQIAMO_PUT_RETRIES]; ok {
296 if intVal, ok := val.(int32); ok {
297 stat.PutRetries = AttributeValue(intVal)
298 }
299 }
300
301 stats = append(stats, stat)
302
303 c.protocol.Debugf("Parsed channel statistics for channel '%s' - messages: %v, bytes: %v",
304 stat.Name, stat.Messages, stat.Bytes)
305
306 return stats, nil
307 }
308
309 // parseMQIStatistics parses MQI statistics from the raw PCF message
310 func (c *Client) parseMQIStatistics(buffer []byte) ([]MQIStatistics, error) {
311 attrs, err := c.parsePCFResponse(buffer, "STATISTICS_MQI")
312 if err != nil {
313 return nil, fmt.Errorf("failed to parse MQI statistics: %w", err)
314 }
315
316 var mqiStats []MQIStatistics
317 var stat MQIStatistics
318
319 // Extract name (could be queue manager or queue name depending on STATMQI setting)
320 if val, ok := attrs[ibmmq.MQCA_Q_MGR_NAME]; ok {
321 if strVal, ok := val.(string); ok {
322 stat.Name = strings.TrimSpace(strVal)
323 }
324 }
325 // If not queue manager name, try queue name
326 if stat.Name == "" {
327 if val, ok := attrs[ibmmq.MQCA_Q_NAME]; ok {
328 if strVal, ok := val.(string); ok {
329 stat.Name = strings.TrimSpace(strVal)
330 }
331 }
332 }
333
334 // Extract MQOPEN operations
335 if val, ok := attrs[ibmmq.MQIAMO_OPENS]; ok {
336 if intVal, ok := val.(int32); ok {
337 stat.Opens = AttributeValue(intVal)
338 }
339 }
340 if val, ok := attrs[ibmmq.MQIAMO_OPENS_FAILED]; ok {
341 if intVal, ok := val.(int32); ok {
342 stat.OpensFailed = AttributeValue(intVal)
343 }
344 }
345
346 // Extract MQCLOSE operations
347 if val, ok := attrs[ibmmq.MQIAMO_CLOSES]; ok {
348 if intVal, ok := val.(int32); ok {
349 stat.Closes = AttributeValue(intVal)
350 }
351 }
352 if val, ok := attrs[ibmmq.MQIAMO_CLOSES_FAILED]; ok {
353 if intVal, ok := val.(int32); ok {
354 stat.ClosesFailed = AttributeValue(intVal)
355 }
356 }
357
358 // Extract MQINQ operations
359 if val, ok := attrs[ibmmq.MQIAMO_INQS]; ok {
360 if intVal, ok := val.(int32); ok {
361 stat.Inqs = AttributeValue(intVal)
362 }
363 }
364 if val, ok := attrs[ibmmq.MQIAMO_INQS_FAILED]; ok {
365 if intVal, ok := val.(int32); ok {
366 stat.InqsFailed = AttributeValue(intVal)
367 }
368 }
369
370 // Extract MQSET operations
371 if val, ok := attrs[ibmmq.MQIAMO_SETS]; ok {
372 if intVal, ok := val.(int32); ok {
373 stat.Sets = AttributeValue(intVal)
374 }
375 }
376 if val, ok := attrs[ibmmq.MQIAMO_SETS_FAILED]; ok {
377 if intVal, ok := val.(int32); ok {
378 stat.SetsFailed = AttributeValue(intVal)
379 }
380 }
381
382 // Extract timestamp information
383 var startDate, startTime, endDate, endTime string
384 if val, ok := attrs[ibmmq.MQCAMO_START_DATE]; ok {
385 if strVal, ok := val.(string); ok {
386 startDate = strings.TrimSpace(strVal)
387 }
388 }
389 if val, ok := attrs[ibmmq.MQCAMO_START_TIME]; ok {
390 if strVal, ok := val.(string); ok {
391 startTime = strings.TrimSpace(strVal)
392 }
393 }
394 if val, ok := attrs[ibmmq.MQCAMO_END_DATE]; ok {
395 if strVal, ok := val.(string); ok {
396 endDate = strings.TrimSpace(strVal)
397 }
398 }
399 if val, ok := attrs[ibmmq.MQCAMO_END_TIME]; ok {
400 if strVal, ok := val.(string); ok {
401 endTime = strings.TrimSpace(strVal)
402 }
403 }
404
405 // Convert timestamps to Unix epoch if we have both date and time
406 // MQCAMO_START_DATE/TIME and MQCAMO_END_DATE/TIME are administrative monitoring timestamps (local time)
407 if startDate != "" && startTime != "" {
408 if startTimestamp, err := ParseMQAdminDateTime(startDate, startTime); err == nil {
409 stat.StartDate = AttributeValue(startTimestamp.Unix())
410 stat.StartTime = AttributeValue(startTimestamp.Unix())
411 }
412 }
413 if endDate != "" && endTime != "" {
414 if endTimestamp, err := ParseMQAdminDateTime(endDate, endTime); err == nil {
415 stat.EndDate = AttributeValue(endTimestamp.Unix())
416 stat.EndTime = AttributeValue(endTimestamp.Unix())
417 }
418 }
419
420 // Add the statistics entry
421 mqiStats = append(mqiStats, stat)
422
423 c.protocol.Debugf("Parsed MQI statistics for '%s' - opens: %v (failed: %v), closes: %v (failed: %v), inqs: %v (failed: %v), sets: %v (failed: %v)",
424 stat.Name, stat.Opens, stat.OpensFailed, stat.Closes, stat.ClosesFailed,
425 stat.Inqs, stat.InqsFailed, stat.Sets, stat.SetsFailed)
426
427 return mqiStats, nil
428 }