Skip to content

Commit da2a19e

Browse files
committed
Add RESET QSTATS for z/OS monitoring
1 parent 127435e commit da2a19e

File tree

5 files changed

+198
-16
lines changed

5 files changed

+198
-16
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Changelog
22
Newest updates are at the top of this file.
3-
## July 11 2018 - v4.0.8
3+
4+
## July 22 2019 - v4.0.9
5+
* mqmetric - Support RESET QSTATS on z/OS queue manager
6+
* mqmetric - Add a Logger class to enable debug output
7+
* mqmetric - Improve some error reports
8+
9+
## July 11 2019 - v4.0.8
410
* Update for MQ 9.1.3 - No new API function introduced
511
* mqmetric - Fix leak in subscriptions after rediscovery
612
* mqmetric - Add USAGE as a queue label for selection by xmitq

mqmetric/discover.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ type AllMetrics struct {
8181
}
8282

8383
type QInfo struct {
84-
MaxDepth int64
85-
Usage int64
86-
exists bool // Used during rediscovery
87-
firstCollection bool // To indicate discard needed of first stat
84+
AttrMaxDepth int64 // The queue attribute value. Not the max depth reported by RESET QSTATS
85+
AttrUsage int64 // Normal or XMITQ
86+
exists bool // Used during rediscovery
87+
firstCollection bool // To indicate discard needed of first stat
8888
}
8989

9090
// QMgrMapKey can never be a real object name and is therefore useful in
@@ -559,7 +559,7 @@ func discoverQueues(monitoredQueuePatterns string) error {
559559
if qInfoElem, ok = qInfoMap[qName]; !ok {
560560
qInfoElem = new(QInfo)
561561
}
562-
qInfoElem.MaxDepth = defaultMaxQDepth
562+
qInfoElem.AttrMaxDepth = defaultMaxQDepth
563563
qInfoElem.exists = true
564564
qInfoMap[qName] = qInfoElem
565565
}
@@ -696,7 +696,8 @@ func inquireObjects(objectPatternsList string, objectType int32) ([]string, erro
696696
truncation = false
697697
cfh, offset := ibmmq.ReadPCFHeader(buf)
698698
if cfh.CompCode != ibmmq.MQCC_OK {
699-
return objectList, fmt.Errorf("PCF command failed with CC %s [%d] RC %s [%d]",
699+
return objectList, fmt.Errorf("PCF command %s [%d] failed with CC %s [%d] RC %s [%d]",
700+
ibmmq.MQItoString("CMD", int(cfh.Command)), cfh.Command,
700701
ibmmq.MQItoString("CC", int(cfh.CompCode)), cfh.CompCode,
701702
ibmmq.MQItoString("RC", int(cfh.Reason)), cfh.Reason)
702703
} else {

mqmetric/log.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package mqmetric
2+
3+
/*
4+
Copyright (c) IBM Corporation 2016, 2019
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
Contributors:
19+
Mark Taylor - Initial Contribution
20+
*/
21+
22+
type Logger struct {
23+
Debug func(string, ...interface{})
24+
Info func(string, ...interface{})
25+
Error func(string, ...interface{})
26+
}
27+
28+
var logger *Logger = nil
29+
30+
func SetLogger(l *Logger) {
31+
logger = l
32+
}
33+
34+
func logDebug(format string, v ...interface{}) {
35+
if logger != nil && logger.Debug != nil {
36+
logger.Debug(format, v...)
37+
}
38+
}
39+
func logInfo(format string, v ...interface{}) {
40+
if logger != nil && logger.Info != nil {
41+
logger.Info(format, v...)
42+
}
43+
}
44+
func logError(format string, v ...interface{}) {
45+
if logger != nil && logger.Info != nil {
46+
logger.Error(format, v...)
47+
}
48+
}

mqmetric/mqif.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ don't need to repeat common setups eg of MQMD or MQSD structures.
2626

2727
import (
2828
"fmt"
29-
3029
"github.com/ibm-messaging/mq-golang/ibmmq"
3130
)
3231

@@ -49,6 +48,7 @@ var (
4948

5049
usePublications = true
5150
useStatus = false
51+
useResetQStats = false
5252
)
5353

5454
type ConnectionConfig struct {
@@ -59,6 +59,7 @@ type ConnectionConfig struct {
5959

6060
UsePublications bool
6161
UseStatus bool
62+
UseResetQStats bool
6263
}
6364

6465
/*
@@ -90,6 +91,7 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error
9091
gocno.SecurityParms = gocsp
9192
}
9293

94+
logDebug("Connecting to queue manager %s", qMgrName)
9395
qMgr, err = ibmmq.Connx(qMgrName, gocno)
9496
if err == nil {
9597
qmgrConnected = true
@@ -117,6 +119,7 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error
117119
if err == nil {
118120
selectors := []int32{ibmmq.MQCA_Q_MGR_NAME,
119121
ibmmq.MQIA_COMMAND_LEVEL,
122+
ibmmq.MQIA_PERFORMANCE_EVENT,
120123
ibmmq.MQIA_PLATFORM}
121124

122125
v, err = qMgrObject.InqMap(selectors)
@@ -126,6 +129,12 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error
126129
commandLevel = v[ibmmq.MQIA_COMMAND_LEVEL].(int32)
127130
if platform == ibmmq.MQPL_ZOS {
128131
usePublications = false
132+
useResetQStats = cc.UseResetQStats
133+
evEnabled := v[ibmmq.MQIA_PERFORMANCE_EVENT].(int32)
134+
if useResetQStats && evEnabled == 0 {
135+
err = fmt.Errorf("Requested use of RESET QSTATS but queue manager has PERFMEV(DISABLED)")
136+
errorString = "Command"
137+
}
129138
} else {
130139
if cc.UsePublications == true {
131140
if commandLevel < 900 && platform != ibmmq.MQPL_APPLIANCE {
@@ -143,6 +152,8 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error
143152
// Don't need the qMgrObject any more
144153
qMgrObject.Close(0)
145154

155+
} else {
156+
errorString = "Cannot open queue manager object"
146157
}
147158
}
148159

@@ -189,7 +200,7 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error
189200
mqod.ObjectName = replyQ
190201
statusReplyQObj, err = qMgr.Open(mqod, openOptions)
191202
if err != nil {
192-
errorString = "Cannot open queue" + replyQ
203+
errorString = "Cannot open queue " + replyQ
193204
}
194205
}
195206

mqmetric/queue.go

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ about MQ queues
3030
*/
3131

3232
import (
33-
"github.com/ibm-messaging/mq-golang/ibmmq"
3433
// "fmt"
34+
"github.com/ibm-messaging/mq-golang/ibmmq"
3535
"strings"
3636
"time"
3737
)
@@ -48,6 +48,17 @@ const (
4848
ATTR_Q_SINCE_GET = "time_since_get"
4949
ATTR_Q_MAX_DEPTH = "attribute_max_depth"
5050
ATTR_Q_USAGE = "attribute_usage"
51+
52+
// The next two attributes are given the same name
53+
// as the published statistics from the amqsrua-style
54+
// vaues. That allows a dashboard for Distributed and z/OS
55+
// to merge the same query.
56+
ATTR_Q_INTERVAL_PUT = "mqput_mqput1_count"
57+
ATTR_Q_INTERVAL_GET = "mqget_count"
58+
// This is the Highest Depth returned over an interval via the
59+
// RESET QSTATS command. Contrast with the attribute_max_depth
60+
// value which is the DISPLAY QL(x) MAXDEPTH attribute.
61+
ATTR_Q_INTERVAL_HI_DEPTH = "hi_depth"
5162
)
5263

5364
var QueueStatus StatusSet
@@ -89,9 +100,19 @@ func QueueInitAttributes() {
89100
QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Depth", ibmmq.MQIA_CURRENT_Q_DEPTH)
90101
}
91102

103+
if platform == ibmmq.MQPL_ZOS && useResetQStats {
104+
attr = ATTR_Q_INTERVAL_PUT
105+
QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Put/Put1 Count", ibmmq.MQIA_MSG_ENQ_COUNT)
106+
attr = ATTR_Q_INTERVAL_GET
107+
QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Get Count", ibmmq.MQIA_MSG_DEQ_COUNT)
108+
attr = ATTR_Q_INTERVAL_HI_DEPTH
109+
QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Highest Depth", ibmmq.MQIA_HIGH_Q_DEPTH)
110+
}
111+
92112
// This is not really a monitoring metric but it enables calculations to be made such as %full for
93113
// the queue. It's extracted at startup of the program via INQUIRE_Q and not updated later even if the
94-
// queue definition is changed. It's not easy to generate the % value in this program as the CurDepth will
114+
// queue definition is changed until rediscovery of the queues on a schedule.
115+
// It's not easy to generate the % value in this program as the CurDepth will
95116
// usually - but not always - come from the published resource stats. So we don't have direct access to it.
96117
// Recording the MaxDepth allows Prometheus etc to do the calculation regardless of how the CurDepth was obtained.
97118
attr = ATTR_Q_MAX_DEPTH
@@ -142,6 +163,9 @@ func CollectQueueStatus(patterns string) error {
142163
}
143164
//fmt.Printf("Collecting qStatus for %s\n",qName)
144165
err = collectQueueStatus(qName, ibmmq.MQOT_Q)
166+
if err == nil && useResetQStats {
167+
err = collectResetQStats(qName)
168+
}
145169
}
146170
} else {
147171
for _, pattern := range queuePatterns {
@@ -150,6 +174,9 @@ func CollectQueueStatus(patterns string) error {
150174
continue
151175
}
152176
err = collectQueueStatus(pattern, ibmmq.MQOT_Q)
177+
if err == nil && useResetQStats {
178+
err = collectResetQStats(pattern)
179+
}
153180
}
154181
}
155182
return err
@@ -204,6 +231,43 @@ func collectQueueStatus(pattern string, instanceType int32) error {
204231
return err
205232
}
206233

234+
func collectResetQStats(pattern string) error {
235+
var err error
236+
237+
statusClearReplyQ()
238+
putmqmd, pmo, cfh, buf := statusSetCommandHeaders()
239+
240+
// Can allow all the other fields to default
241+
cfh.Command = ibmmq.MQCMD_RESET_Q_STATS
242+
243+
// Add the parameters one at a time into a buffer
244+
pcfparm := new(ibmmq.PCFParameter)
245+
pcfparm.Type = ibmmq.MQCFT_STRING
246+
pcfparm.Parameter = ibmmq.MQCA_Q_NAME
247+
pcfparm.String = []string{pattern}
248+
cfh.ParameterCount++
249+
buf = append(buf, pcfparm.Bytes()...)
250+
251+
buf = append(cfh.Bytes(), buf...)
252+
253+
// And now put the command to the queue
254+
err = cmdQObj.Put(putmqmd, pmo, buf)
255+
if err != nil {
256+
return err
257+
}
258+
259+
// Now get the responses - loop until all have been received (one
260+
// per queue) or we run out of time
261+
for allReceived := false; !allReceived; {
262+
cfh, buf, allReceived, err = statusGetReply()
263+
if buf != nil {
264+
parseResetQStatsData(cfh, buf)
265+
}
266+
}
267+
268+
return err
269+
}
270+
207271
// Issue the INQUIRE_Q call for wildcarded queue names and
208272
// extract the required attributes - currently, just the
209273
// Maximum Queue Depth
@@ -334,16 +398,68 @@ func parseQData(instanceType int32, cfh *ibmmq.MQCFH, buf []byte) string {
334398
now := time.Now()
335399
QueueStatus.Attributes[ATTR_Q_SINCE_PUT].Values[key] = newStatusValueInt64(statusTimeDiff(now, lastPutDate, lastPutTime))
336400
QueueStatus.Attributes[ATTR_Q_SINCE_GET].Values[key] = newStatusValueInt64(statusTimeDiff(now, lastGetDate, lastGetTime))
337-
338401
if s, ok := qInfoMap[key]; ok {
339-
maxDepth := s.MaxDepth
402+
maxDepth := s.AttrMaxDepth
340403
QueueStatus.Attributes[ATTR_Q_MAX_DEPTH].Values[key] = newStatusValueInt64(maxDepth)
341-
usage := s.Usage
404+
usage := s.AttrUsage
342405
QueueStatus.Attributes[ATTR_Q_USAGE].Values[key] = newStatusValueInt64(usage)
343406
}
344407
return key
345408
}
346409

410+
// Given a PCF response message, parse it to extract the desired statistics
411+
func parseResetQStatsData(cfh *ibmmq.MQCFH, buf []byte) string {
412+
var elem *ibmmq.PCFParameter
413+
414+
qName := ""
415+
key := ""
416+
417+
parmAvail := true
418+
bytesRead := 0
419+
offset := 0
420+
datalen := len(buf)
421+
if cfh == nil || cfh.ParameterCount == 0 {
422+
return ""
423+
}
424+
425+
// Parse it once to extract the fields that are needed for the map key
426+
for parmAvail && cfh.CompCode != ibmmq.MQCC_FAILED {
427+
elem, bytesRead = ibmmq.ReadPCFParameter(buf[offset:])
428+
offset += bytesRead
429+
// Have we now reached the end of the message
430+
if offset >= datalen {
431+
parmAvail = false
432+
}
433+
434+
// Only one field needed for queues
435+
switch elem.Parameter {
436+
case ibmmq.MQCA_Q_NAME:
437+
qName = strings.TrimSpace(elem.String[0])
438+
}
439+
}
440+
441+
// Create a unique key for this instance
442+
key = qName
443+
444+
QueueStatus.Attributes[ATTR_Q_NAME].Values[key] = newStatusValueString(qName)
445+
446+
// And then re-parse the message so we can store the metrics now knowing the map key
447+
parmAvail = true
448+
offset = 0
449+
for parmAvail && cfh.CompCode != ibmmq.MQCC_FAILED {
450+
elem, bytesRead = ibmmq.ReadPCFParameter(buf[offset:])
451+
offset += bytesRead
452+
// Have we now reached the end of the message
453+
if offset >= datalen {
454+
parmAvail = false
455+
}
456+
457+
statusGetIntAttributes(QueueStatus, elem, key)
458+
}
459+
460+
return key
461+
}
462+
347463
func parseQAttrData(cfh *ibmmq.MQCFH, buf []byte) {
348464
var elem *ibmmq.PCFParameter
349465

@@ -388,15 +504,15 @@ func parseQAttrData(cfh *ibmmq.MQCFH, buf []byte) {
388504
v := elem.Int64Value[0]
389505
if v > 0 {
390506
if qInfo, ok := qInfoMap[qName]; ok {
391-
qInfo.MaxDepth = v
507+
qInfo.AttrMaxDepth = v
392508
}
393509
}
394510
//fmt.Printf("MaxQDepth for %s = %d \n",qName,v)
395511
case ibmmq.MQIA_USAGE:
396512
v := elem.Int64Value[0]
397513
if v > 0 {
398514
if qInfo, ok := qInfoMap[qName]; ok {
399-
qInfo.Usage = v
515+
qInfo.AttrUsage = v
400516
}
401517
}
402518
}

0 commit comments

Comments
 (0)