From 86966e40498ff063557488dec8405f32059ba10c Mon Sep 17 00:00:00 2001 From: ibmmqmet Date: Tue, 13 Nov 2018 19:32:04 +0000 Subject: [PATCH 1/4] Add QSTATUS options --- CHANGELOG.md | 3 + README.md | 68 ++++-- ibmmq/mqi.go | 7 +- mqmetric/channel.go | 10 +- mqmetric/discover.go | 6 +- mqmetric/queue.go | 343 +++++++++++++++++++++++++++++++ mqmetric/status.go | 2 + samples/clientconn/clientconn.go | 7 +- 8 files changed, 415 insertions(+), 31 deletions(-) create mode 100644 mqmetric/queue.go diff --git a/CHANGELOG.md b/CHANGELOG.md index dda0513..dfc0afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ======= +### November 2018 +* Added functions to mqmetric to issue DISPLAY QSTATUS for additional stats + ### October 2018 * Allow compilation against MQ v8 diff --git a/README.md b/README.md index f5bd9da..edeace6 100755 --- a/README.md +++ b/README.md @@ -4,41 +4,58 @@ This repository demonstrates how you can call IBM MQ from applications written i > **NOTICE**: Please ensure that you use a dependency management tool such as [dep](https://github.com/golang/dep) or [Glide](http://glide.sh/), and add a specific version dependency. -This repository previously contained sample programs that exported MQ statistics to some monitoring packages. These have now been moved to a new [GitHub repository called mq-metric-samples](https://github.com/ibm-messaging/mq-metric-samples). +This repository previously contained sample programs that exported MQ statistics to +some monitoring packages. These have now been moved to a +new [GitHub repository called mq-metric-samples](https://github.com/ibm-messaging/mq-metric-samples). -A minimum level of MQ V8 is required to build these packages. -However, note that the monitoring data published by the queue manager is not available before MQ V9. +A minimum level of MQ V8 is required to build these packages. However, note that +the monitoring data published by the queue manager is not available before MQ V9. ## Health Warning -This package is provided as-is with no guarantees of support or updates. There are also no guarantees of compatibility with any future versions of the package; the API is subject to change based on any feedback. +This package is provided as-is with no guarantees of support or updates. There are +also no guarantees of compatibility with any future versions of the package; the API +is subject to change based on any feedback. Versioned releases are made in this repository +to assist with using stable APIs. ## MQI Description The ibmmq directory contains a Go package, exposing an MQI-like interface. -The intention is to give an API that is more natural for Go programmers than the common procedural MQI. For example, fixed length string arrays from the C API such as MQCHAR48 are represented by the native Go string type. Conversion between these types is handled within the ibmmq package itself, removing the need for Go programmers to know about it. +The intention is to give an API that is more natural for Go programmers than the +common procedural MQI. For example, fixed length string arrays from the C API such +as MQCHAR48 are represented by the native Go string type. Conversion between these +types is handled within the ibmmq package itself, removing the need for Go programmers +to know about it. -A short program in the samples/mqitest directory gives an example of using this interface, to put and get messages and to subscribe to a topic. +A short program in the samples/mqitest directory gives an example of using this interface, +to put and get messages and to subscribe to a topic. The mqmetric directory contains functions to help monitoring programs access MQ status and statistics. This package is not needed for general application programs. -Feedback on the utility of this package, thoughts about whether it should be changed or extended are welcomed. - ## Using the package -To use code in this repository, you will need to be able to build Go applications, and have a copy of MQ installed to build against. It uses cgo to access the MQI C structures and definitions. It assumes that MQ has been installed in the default location on a Linux platform (`/opt/mqm`) but you can easily change the cgo directives in the source files if necessary. +To use code in this repository, you will need to be able to build Go applications, and +have a copy of MQ installed to build against. It uses cgo to access the MQI C +structures and definitions. It assumes that MQ has been installed in the default +location (on a Linux platform this would be `/opt/mqm`) but this can be changed +with environment variables if necessary. -Some Windows capability is also included. This has been tested with Go 1.10 compiler, which now permits standard Windows paths (eg including spaces) so the CGO directives can point at the normal MQ install path. +Windows compatibility is also included. This has been tested with Go 1.10 compiler, +which now permits standard Windows paths (eg including spaces) so the CGO directives +can point at the normal MQ install path. ## Getting started -If you are unfamiliar with Go, the following steps can help create a working environment with source code in a suitable tree. Initial setup tends to be platform-specific, but subsequent steps are independent of the platform. +If you are unfamiliar with Go, the following steps can help create a working environment +with source code in a suitable tree. Initial setup tends to be platform-specific, +but subsequent steps are independent of the platform. ### Linux -* Install the Go runtime and compiler. On Linux, the packaging may vary but a typical directory for the code is `/usr/lib/golang`. +* Install the Go runtime and compiler. On Linux, the packaging may vary but a typical +directory for the code is `/usr/lib/golang`. * Create a working directory. For example, ```mkdir $HOME/gowork``` @@ -81,6 +98,14 @@ set CC=x86_64-w64-mingw32-gcc.exe `git clone https://github.com/ibm-messaging/mq-golang.git src/github.com/ibm-messaging/mq-golang` +* If you have not installed MQ libraries into the default location, then set environment variables +for the C compiler to recognise those directories. For example, + +``` + export CGO_CFLAGS="-I/my/mq/dir/inc" + export CGO_LDFLAGS="-I/my/mq/dir/lib64" +``` + * Compile the `ibmmq` component: `go install ./src/github.com/ibm-messaging/mq-golang/ibmmq` @@ -89,9 +114,11 @@ set CC=x86_64-w64-mingw32-gcc.exe `go install ./src/github.com/ibm-messaging/mq-golang/mqmetric` -* Follow the instructions in the [mq-metric-samples repository](https://github.com/ibm-messaging/mq-metric-samples) to compile the sample programs you are interested in. +* Sample programs can be compiled in this way + + `go build -o bin/mqitest ./src/github.com/ibm-messaging/mq-golang/samples/mqitest/*.go` -At this point, you should have a compiled copy of the code in `$GOPATH/bin`. +At this point, you should have a compiled copy of the program in `$GOPATH/bin`. ## Limitations @@ -111,11 +138,16 @@ See [CHANGELOG](CHANGELOG.md) in this directory. ## Issues and Contributions -For feedback and issues relating specifically to this package, please use the [GitHub issue tracker](https://github.com/ibm-messaging/mq-golang/issues). +Feedback on the utility of this package, thoughts about whether it should be changed +or extended are welcomed. + +For feedback and issues relating specifically to this package, please use +the [GitHub issue tracker](https://github.com/ibm-messaging/mq-golang/issues). -Contributions to this package can be accepted under the terms of the IBM Contributor License Agreement, -found in the [CLA file](CLA.md) of this repository. When submitting a pull request, you must include a statement stating -you accept the terms in the CLA. +Contributions to this package can be accepted under the terms of the IBM Contributor +License Agreement, found in the [CLA file](CLA.md) of this repository. When +submitting a pull request, you must include a statement stating you accept the terms +in the CLA. ## Copyright diff --git a/ibmmq/mqi.go b/ibmmq/mqi.go index 512c5ad..6a62fba 100644 --- a/ibmmq/mqi.go +++ b/ibmmq/mqi.go @@ -16,13 +16,14 @@ the particular MQRC or MQCC values. The build directives assume the default MQ installation path which is in /opt/mqm (Linux) and c:\Program Files\IBM\MQ (Windows). -These would need to be changed in this file if you use a -non-default path. +If you use a non-default path for the installation, you can set +environment variables CGO_CFLAGS and CGO_LDFLAGS to reference those +directories. */ package ibmmq /* - Copyright (c) IBM Corporation 2016 + Copyright (c) IBM Corporation 2016, 2018 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/mqmetric/channel.go b/mqmetric/channel.go index 8a6611a..eff6f70 100644 --- a/mqmetric/channel.go +++ b/mqmetric/channel.go @@ -53,7 +53,7 @@ const ( ) var ChannelStatus StatusSet -var attrsInit = false +var chlAttrsInit = false var channelsSeen map[string]bool /* @@ -65,7 +65,7 @@ text. The elements can be expanded later; just trying to give a starting point for now. */ func ChannelInitAttributes() { - if attrsInit { + if chlAttrsInit { return } ChannelStatus.Attributes = make(map[string]*StatusAttribute) @@ -98,7 +98,7 @@ func ChannelInitAttributes() { attr = ATTR_CHL_STATUS_SQUASH ChannelStatus.Attributes[attr] = newStatusAttribute(attr, "Channel Status - Simplified", ibmmq.MQIACH_CHANNEL_STATUS) ChannelStatus.Attributes[attr].squash = true - attrsInit = true + chlAttrsInit = true } // If we need to list the channels that match a pattern. Not needed for @@ -252,7 +252,7 @@ func collectChannelStatus(pattern string, instanceType int32) error { if cfh.Reason != ibmmq.MQRC_NONE { continue } - key := parseData(instanceType, cfh, replyBuf[offset:datalen]) + key := parseChlData(instanceType, cfh, replyBuf[offset:datalen]) if key != "" { channelsSeen[key] = true } @@ -263,7 +263,7 @@ func collectChannelStatus(pattern string, instanceType int32) error { } // Given a PCF response message, parse it to extract the desired statistics -func parseData(instanceType int32, cfh *ibmmq.MQCFH, buf []byte) string { +func parseChlData(instanceType int32, cfh *ibmmq.MQCFH, buf []byte) string { var elem *ibmmq.PCFParameter chlName := "" diff --git a/mqmetric/discover.go b/mqmetric/discover.go index 23714ff..8b66e35 100755 --- a/mqmetric/discover.go +++ b/mqmetric/discover.go @@ -90,6 +90,10 @@ var Metrics AllMetrics var qList []string +func GetDiscoveredQueues() []string { + return qList +} + /* DiscoverAndSubscribe does all the work of finding the different resources available from a queue manager and @@ -349,7 +353,7 @@ func discoverQueues(monitoredQueues string) error { var err error qList, err = inquireObjects(monitoredQueues, ibmmq.MQOT_Q) if len(qList) > 0 { - fmt.Printf("Monitoring Queues: %v\n", qList) + //fmt.Printf("Monitoring Queues: %v\n", qList) if err != nil { // fmt.Printf("Queue Discovery: %v\n", err) } diff --git a/mqmetric/queue.go b/mqmetric/queue.go new file mode 100644 index 0000000..df78484 --- /dev/null +++ b/mqmetric/queue.go @@ -0,0 +1,343 @@ +/* +Package mqmetric contains a set of routines common to several +commands used to export MQ metrics to different backend +storage mechanisms including Prometheus and InfluxDB. +*/ +package mqmetric + +/* + Copyright (c) IBM Corporation 2016, 2018 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Contributors: + Mark Taylor - Initial Contribution +*/ + +/* +Functions in this file use the DISPLAY QueueStatus command to extract metrics +about MQ queues +*/ + +import ( + "github.com/ibm-messaging/mq-golang/ibmmq" + "strings" +) + +const ( + ATTR_Q_NAME = "name" + ATTR_Q_MSGAGE = "oldest_message_age" + ATTR_Q_IPPROCS = "input_handles" + ATTR_Q_OPPROCS = "output_handles" + ATTR_Q_QTIME_SHORT = "qtime_short" + ATTR_Q_QTIME_LONG = "qtime_long" +) + +var QueueStatus StatusSet +var qAttrsInit = false + +/* +Unlike the statistics produced via a topic, there is no discovery +of the attributes available in object STATUS queries. There is also +no discovery of descriptions for them. So this function hardcodes the +attributes we are going to look for and gives the associated descriptive +text. The elements can be expanded later; just trying to give a starting point +for now. +*/ +func QueueInitAttributes() { + if qAttrsInit { + return + } + QueueStatus.Attributes = make(map[string]*StatusAttribute) + + attr := ATTR_Q_NAME + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Name", -1) + + // These are the integer status fields that are of interest + attr = ATTR_Q_MSGAGE + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Oldest Message", ibmmq.MQIACF_OLDEST_MSG_AGE) + attr = ATTR_Q_IPPROCS + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Input Handles", ibmmq.MQIA_OPEN_INPUT_COUNT) + attr = ATTR_Q_OPPROCS + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Input Handles", ibmmq.MQIA_OPEN_OUTPUT_COUNT) + + attr = ATTR_Q_QTIME_SHORT + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Time Short", ibmmq.MQIACF_Q_TIME_INDICATOR) + QueueStatus.Attributes[attr].index = 0 + attr = ATTR_Q_QTIME_LONG + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Time Long", ibmmq.MQIACF_Q_TIME_INDICATOR) + QueueStatus.Attributes[attr].index = 1 + + qAttrsInit = true +} + +// If we need to list the queues that match a pattern. Not needed for +// the status queries as they (unlike the pub/sub resource stats) accept +// patterns in the +func InquireQueues(patterns string) ([]string, error) { + QueueInitAttributes() + return inquireObjects(patterns, ibmmq.MQOT_Q) +} + +func CollectQueueStatus(patterns string) error { + var err error + + QueueInitAttributes() + + // Empty any collected values + for k := range QueueStatus.Attributes { + QueueStatus.Attributes[k].Values = make(map[string]*StatusValue) + } + + queuePatterns := strings.Split(patterns, ",") + if len(queuePatterns) == 0 { + return nil + } + + for _, pattern := range queuePatterns { + pattern = strings.TrimSpace(pattern) + if len(pattern) == 0 { + continue + } + + err = collectQueueStatus(pattern, ibmmq.MQOT_Q) + + } + + return err + +} + +// Issue the INQUIRE_QUEUE_STATUS command for a queue or wildcarded queue name +// Collect the responses and build up the statistics +func collectQueueStatus(pattern string, instanceType int32) error { + var err error + var datalen int + + putmqmd := ibmmq.NewMQMD() + pmo := ibmmq.NewMQPMO() + + pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT + pmo.Options |= ibmmq.MQPMO_NEW_MSG_ID + pmo.Options |= ibmmq.MQPMO_NEW_CORREL_ID + pmo.Options |= ibmmq.MQPMO_FAIL_IF_QUIESCING + + putmqmd.Format = "MQADMIN" + putmqmd.ReplyToQ = statusReplyQObj.Name + putmqmd.MsgType = ibmmq.MQMT_REQUEST + putmqmd.Report = ibmmq.MQRO_PASS_DISCARD_AND_EXPIRY + + buf := make([]byte, 0) + // Empty replyQ in case any left over from previous errors + for ok := true; ok; { + getmqmd := ibmmq.NewMQMD() + gmo := ibmmq.NewMQGMO() + gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT + gmo.Options |= ibmmq.MQGMO_FAIL_IF_QUIESCING + gmo.Options |= ibmmq.MQGMO_NO_WAIT + gmo.Options |= ibmmq.MQGMO_CONVERT + gmo.Options |= ibmmq.MQGMO_ACCEPT_TRUNCATED_MSG + _, err = statusReplyQObj.Get(getmqmd, gmo, buf) + + if err != nil && err.(*ibmmq.MQReturn).MQCC == ibmmq.MQCC_FAILED { + ok = false + } + } + buf = make([]byte, 0) + + cfh := ibmmq.NewMQCFH() + + // Can allow all the other fields to default + cfh.Command = ibmmq.MQCMD_INQUIRE_Q_STATUS + + // Add the parameters one at a time into a buffer + pcfparm := new(ibmmq.PCFParameter) + pcfparm.Type = ibmmq.MQCFT_STRING + pcfparm.Parameter = ibmmq.MQCA_Q_NAME + pcfparm.String = []string{pattern} + cfh.ParameterCount++ + buf = append(buf, pcfparm.Bytes()...) + + pcfparm = new(ibmmq.PCFParameter) + pcfparm.Type = ibmmq.MQCFT_INTEGER + pcfparm.Parameter = ibmmq.MQIACF_Q_STATUS_TYPE + pcfparm.Int64Value = []int64{int64(ibmmq.MQIACF_Q_STATUS)} + cfh.ParameterCount++ + buf = append(buf, pcfparm.Bytes()...) + + // Once we know the total number of parameters, put the + // CFH header on the front of the buffer. + buf = append(cfh.Bytes(), buf...) + + // And now put the command to the queue + err = cmdQObj.Put(putmqmd, pmo, buf) + if err != nil { + return err + + } + + // Now get the responses - loop until all have been received (one + // per queue) or we run out of time + replyBuf := make([]byte, 10240) + for allReceived := false; !allReceived; { + getmqmd := ibmmq.NewMQMD() + gmo := ibmmq.NewMQGMO() + gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT + gmo.Options |= ibmmq.MQGMO_FAIL_IF_QUIESCING + gmo.Options |= ibmmq.MQGMO_WAIT + gmo.Options |= ibmmq.MQGMO_CONVERT + gmo.WaitInterval = 3 * 1000 // 3 seconds + + datalen, err = statusReplyQObj.Get(getmqmd, gmo, replyBuf) + if err == nil { + cfh, offset := ibmmq.ReadPCFHeader(replyBuf) + + if cfh.Control == ibmmq.MQCFC_LAST { + allReceived = true + } + if cfh.Reason != ibmmq.MQRC_NONE { + continue + } + parseQData(instanceType, cfh, replyBuf[offset:datalen]) + + } + } + + return err +} + +// Given a PCF response message, parse it to extract the desired statistics +func parseQData(instanceType int32, cfh *ibmmq.MQCFH, buf []byte) string { + var elem *ibmmq.PCFParameter + + qName := "" + key := "" + + parmAvail := true + bytesRead := 0 + offset := 0 + datalen := len(buf) + if cfh.ParameterCount == 0 { + return "" + } + + // Parse it once to extract the fields that are needed for the map key + for parmAvail && cfh.CompCode != ibmmq.MQCC_FAILED { + elem, bytesRead = ibmmq.ReadPCFParameter(buf[offset:]) + offset += bytesRead + // Have we now reached the end of the message + if offset >= datalen { + parmAvail = false + } + + switch elem.Parameter { + case ibmmq.MQCA_Q_NAME: + qName = strings.TrimSpace(elem.String[0]) + } + } + + // Create a unique key for this instance + key = qName + + QueueStatus.Attributes[ATTR_Q_NAME].Values[key] = newStatusValueString(qName) + + // And then re-parse the message so we can store the metrics now knowing the map key + parmAvail = true + offset = 0 + for parmAvail && cfh.CompCode != ibmmq.MQCC_FAILED { + elem, bytesRead = ibmmq.ReadPCFParameter(buf[offset:]) + offset += bytesRead + // Have we now reached the end of the message + if offset >= datalen { + parmAvail = false + } + + // Look at the Parameter and loop through all the possible status + // attributes to find it.We don't break from the loop after finding a match + // because there might be more than one attribute associated with the + // attribute (in particular status/status_squash) + usableValue := false + if elem.Type == ibmmq.MQCFT_INTEGER || elem.Type == ibmmq.MQCFT_INTEGER64 { + usableValue = true + } else if elem.Type == ibmmq.MQCFT_INTEGER_LIST || elem.Type == ibmmq.MQCFT_INTEGER64_LIST { + usableValue = true + } + + if usableValue { + for attr, _ := range QueueStatus.Attributes { + if QueueStatus.Attributes[attr].pcfAttr == elem.Parameter { + index := QueueStatus.Attributes[attr].index + if index == -1 { + v := elem.Int64Value[0] + if QueueStatus.Attributes[attr].delta { + // If we have already got a value for this attribute and queue + // then use it to create the delta. Otherwise make the initial + // value 0. + if prevVal, ok := QueueStatus.Attributes[attr].prevValues[key]; ok { + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(v - prevVal) + } else { + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(0) + } + QueueStatus.Attributes[attr].prevValues[key] = v + } else { + // Return the actual number + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(v) + } + } else { + v := elem.Int64Value + if QueueStatus.Attributes[attr].delta { + // If we have already got a value for this attribute and queue + // then use it to create the delta. Otherwise make the initial + // value 0. + if prevVal, ok := QueueStatus.Attributes[attr].prevValues[key]; ok { + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(v[index] - prevVal) + } else { + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(0) + } + QueueStatus.Attributes[attr].prevValues[key] = v[index] + } else { + // Return the actual number + QueueStatus.Attributes[attr].Values[key] = newStatusValueInt64(v[index]) + } + } + } + } + } + } + + return key +} + +// Return a standardised value. If the attribute indicates that something +// special has to be done, then do that. Otherwise just make sure it's a non-negative +// value of the correct datatype +func QueueNormalise(attr *StatusAttribute, v int64) float64 { + var f float64 + + if attr.squash { + switch attr.pcfAttr { + // No queue status values need squashing right now + default: + f = float64(v) + if f < 0 { + f = 0 + } + } + } else { + f = float64(v) + if f < 0 { + f = 0 + } + } + return f +} diff --git a/mqmetric/status.go b/mqmetric/status.go index e448e03..c2e1143 100644 --- a/mqmetric/status.go +++ b/mqmetric/status.go @@ -35,6 +35,7 @@ type StatusAttribute struct { pcfAttr int32 squash bool delta bool + index int Values map[string]*StatusValue prevValues map[string]int64 } @@ -59,6 +60,7 @@ func newStatusAttribute(n string, d string, p int32) *StatusAttribute { s.pcfAttr = p s.squash = false s.delta = false + s.index = -1 s.Values = make(map[string]*StatusValue) s.prevValues = make(map[string]int64) return s diff --git a/samples/clientconn/clientconn.go b/samples/clientconn/clientconn.go index 30d8911..22ba839 100644 --- a/samples/clientconn/clientconn.go +++ b/samples/clientconn/clientconn.go @@ -78,15 +78,14 @@ func main() { // and indicate that we want to use the client // connection method. if true { - cno.ClientConn = cd - } else { + cno.ClientConn = cd + } else { // This is how you might reference a remote CCDT instead of // explicitly putting in the CD structure. - cno.CCDTUrl = "http://localhost:3030" + cno.CCDTUrl = "http://localhost:3030" } cno.Options = ibmmq.MQCNO_CLIENT_BINDING - // Also fill in the userid and password if the MQSAMP_USER_ID // environment variable is set. This is the same as the C // sample programs such as amqsput. From dd9fe35e0b907dbf6a271b5e079c10bc7ef1b1f7 Mon Sep 17 00:00:00 2001 From: ibmmqmet Date: Fri, 16 Nov 2018 18:42:29 +0000 Subject: [PATCH 2/4] Add qstatus and ability to access z/OS status --- CHANGELOG.md | 1 + ibmmq/mqiMQCNO.go | 6 ++++-- ibmmq/mqiPCF.go | 9 +++++++++ mqmetric/channel.go | 2 ++ mqmetric/discover.go | 15 +++++++++++++-- mqmetric/mqif.go | 36 +++++++++++++++++++++++++++++++----- mqmetric/queue.go | 8 ++++++++ 7 files changed, 68 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc0afd..b7e7618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ======= ### November 2018 * Added functions to mqmetric to issue DISPLAY QSTATUS for additional stats +* Added z/OS capability for minimal status ### October 2018 * Allow compilation against MQ v8 diff --git a/ibmmq/mqiMQCNO.go b/ibmmq/mqiMQCNO.go index 36026b3..035b91e 100644 --- a/ibmmq/mqiMQCNO.go +++ b/ibmmq/mqiMQCNO.go @@ -42,7 +42,7 @@ void setCCDTUrl(MQCNO *mqcno, PMQCHAR url, MQLONG length) { mqcno->CCDTUrlOffset = 0; mqcno->CCDTUrlPtr = NULL; mqcno->CCDTUrlLength = length; - if (url != NULL) { + if (url != NULL && length > 0) { mqcno->CCDTUrlPtr = url; } #else @@ -181,7 +181,9 @@ func copyCNOtoC(mqcno *C.MQCNO, gocno *MQCNO) { // The CCDT URL option was introduced in MQ V9. To compile against older // versions of MQ, setting of it has been moved to a C function that can use // the pre-processor to decide whether it's needed. - C.setCCDTUrl(mqcno, C.PMQCHAR(C.CString(gocno.CCDTUrl)), C.MQLONG(len(gocno.CCDTUrl))) + if gocno.CCDTUrl != "" { + C.setCCDTUrl(mqcno, C.PMQCHAR(C.CString(gocno.CCDTUrl)), C.MQLONG(len(gocno.CCDTUrl))) + } return } diff --git a/ibmmq/mqiPCF.go b/ibmmq/mqiPCF.go index 94111a6..49f0864 100644 --- a/ibmmq/mqiPCF.go +++ b/ibmmq/mqiPCF.go @@ -252,6 +252,15 @@ func ReadPCFParameter(buf []byte) (*PCFParameter, int) { case C.MQCFT_GROUP: binary.Read(p, endian, &pcfParm.Parameter) binary.Read(p, endian, &pcfParm.ParameterCount) + + case C.MQCFT_BYTE_STRING: + // For now, the data is not actually stored anywhere as we don't need it + // But we do need to know how to step over the field + offset := int32(C.MQCFBS_STRUC_LENGTH_FIXED) + binary.Read(p, endian, &pcfParm.Parameter) + binary.Read(p, endian, &pcfParm.stringLength) + p.Next(int(pcfParm.strucLength - offset)) + default: fmt.Println("mqiPCF.go: Unknown PCF type ", pcfParm.Type) // Skip the remains of this structure, assuming it really is diff --git a/mqmetric/channel.go b/mqmetric/channel.go index eff6f70..3531bcc 100644 --- a/mqmetric/channel.go +++ b/mqmetric/channel.go @@ -199,6 +199,8 @@ func collectChannelStatus(pattern string, instanceType int32) error { buf = make([]byte, 0) cfh := ibmmq.NewMQCFH() + cfh.Version = ibmmq.MQCFH_VERSION_3 + cfh.Type = ibmmq.MQCFT_COMMAND_XR // Can allow all the other fields to default cfh.Command = ibmmq.MQCMD_INQUIRE_CHANNEL_STATUS diff --git a/mqmetric/discover.go b/mqmetric/discover.go index 8b66e35..fd57779 100755 --- a/mqmetric/discover.go +++ b/mqmetric/discover.go @@ -137,9 +137,9 @@ func discoverClasses(metaPrefix string) error { // Have to know the starting point for the topic that tells about classes if metaPrefix == "" { - rootTopic = "$SYS/MQ/INFO/QMGR/" + qMgr.Name + "/Monitor/METADATA/CLASSES" + rootTopic = "$SYS/MQ/INFO/QMGR/" + resolvedQMgrName + "/Monitor/METADATA/CLASSES" } else { - rootTopic = metaPrefix + "/INFO/QMGR/" + qMgr.Name + "/Monitor/METADATA/CLASSES" + rootTopic = metaPrefix + "/INFO/QMGR/" + resolvedQMgrName + "/Monitor/METADATA/CLASSES" } sub, err = subscribe(rootTopic) if err == nil { @@ -296,6 +296,11 @@ func discoverStats(metaPrefix string) error { // Start with an empty set of information about the available stats Metrics.Classes = make(map[int]*MonClass) + // Allow us to proceed on z/OS even though it does not support pub/sub resources + if metaPrefix == "" && platform == ibmmq.MQPL_ZOS { + return nil + } + // Then get the list of CLASSES err = discoverClasses(metaPrefix) @@ -418,6 +423,8 @@ func inquireObjects(objectPatternsList string, objectType int32) ([]string, erro putmqmd.Report = ibmmq.MQRO_PASS_DISCARD_AND_EXPIRY cfh := ibmmq.NewMQCFH() + cfh.Version = ibmmq.MQCFH_VERSION_3 + cfh.Type = ibmmq.MQCFT_COMMAND_XR // Can allow all the other fields to default cfh.Command = command @@ -572,6 +579,10 @@ func ProcessPublications() error { var elementidx int var value int64 + if platform == ibmmq.MQPL_ZOS { + return nil + } + // Keep reading all available messages until queue is empty. Don't // do a GET-WAIT; just immediate removals. cnt := 0 diff --git a/mqmetric/mqif.go b/mqmetric/mqif.go index e1dc8b4..34bab6e 100644 --- a/mqmetric/mqif.go +++ b/mqmetric/mqif.go @@ -28,14 +28,17 @@ import ( "fmt" "github.com/ibm-messaging/mq-golang/ibmmq" + "strings" ) var ( - qMgr ibmmq.MQQueueManager - cmdQObj ibmmq.MQObject - replyQObj ibmmq.MQObject - statusReplyQObj ibmmq.MQObject - getBuffer = make([]byte, 32768) + qMgr ibmmq.MQQueueManager + cmdQObj ibmmq.MQObject + replyQObj ibmmq.MQObject + statusReplyQObj ibmmq.MQObject + getBuffer = make([]byte, 32768) + platform int32 + resolvedQMgrName string qmgrConnected = false queuesOpened = false @@ -78,6 +81,29 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error qmgrConnected = true } + if err == nil { + mqod := ibmmq.NewMQOD() + openOptions := ibmmq.MQOO_INQUIRE + ibmmq.MQOO_FAIL_IF_QUIESCING + + mqod.ObjectType = ibmmq.MQOT_Q_MGR + mqod.ObjectName = "" + + qMgrObject, err := qMgr.Open(mqod, openOptions) + + if err == nil { + selectors := []int32{ibmmq.MQCA_Q_MGR_NAME, + ibmmq.MQIA_PLATFORM} + + intAttrs, charAttrs, err := qMgrObject.Inq(selectors, 1, 48) + + if err == nil { + resolvedQMgrName = strings.TrimSpace(string(charAttrs[0:48])) + platform = intAttrs[0] + } + + } + } + // MQOPEN of the COMMAND QUEUE if err == nil { mqod := ibmmq.NewMQOD() diff --git a/mqmetric/queue.go b/mqmetric/queue.go index df78484..7657429 100644 --- a/mqmetric/queue.go +++ b/mqmetric/queue.go @@ -41,6 +41,7 @@ const ( ATTR_Q_OPPROCS = "output_handles" ATTR_Q_QTIME_SHORT = "qtime_short" ATTR_Q_QTIME_LONG = "qtime_long" + ATTR_Q_DEPTH = "depth" ) var QueueStatus StatusSet @@ -70,6 +71,11 @@ func QueueInitAttributes() { QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Input Handles", ibmmq.MQIA_OPEN_INPUT_COUNT) attr = ATTR_Q_OPPROCS QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Input Handles", ibmmq.MQIA_OPEN_OUTPUT_COUNT) + // Usually we get the QDepth from published resources, But on z/OS we can get it from the QSTATUS response + if platform == ibmmq.MQPL_ZOS { + attr = ATTR_Q_DEPTH + QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Depth", ibmmq.MQIA_CURRENT_Q_DEPTH) + } attr = ATTR_Q_QTIME_SHORT QueueStatus.Attributes[attr] = newStatusAttribute(attr, "Queue Time Short", ibmmq.MQIACF_Q_TIME_INDICATOR) @@ -156,6 +162,8 @@ func collectQueueStatus(pattern string, instanceType int32) error { buf = make([]byte, 0) cfh := ibmmq.NewMQCFH() + cfh.Version = ibmmq.MQCFH_VERSION_3 + cfh.Type = ibmmq.MQCFT_COMMAND_XR // Can allow all the other fields to default cfh.Command = ibmmq.MQCMD_INQUIRE_Q_STATUS From 07010cd43de78b3c8979bc9606a47906993f5df1 Mon Sep 17 00:00:00 2001 From: ibmmqmet Date: Tue, 20 Nov 2018 07:46:02 +0000 Subject: [PATCH 3/4] Close qmgr handle --- mqmetric/mqif.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mqmetric/mqif.go b/mqmetric/mqif.go index 34bab6e..bba212e 100644 --- a/mqmetric/mqif.go +++ b/mqmetric/mqif.go @@ -100,6 +100,8 @@ func InitConnection(qMgrName string, replyQ string, cc *ConnectionConfig) error resolvedQMgrName = strings.TrimSpace(string(charAttrs[0:48])) platform = intAttrs[0] } + // Don't need the qMgrObject any more + qMgrObject.Close(0) } } From b429947d929e74855f538bf978aa50a4ccf54505 Mon Sep 17 00:00:00 2001 From: ibmmqmet Date: Tue, 20 Nov 2018 09:04:59 +0000 Subject: [PATCH 4/4] copyright dates --- ibmmq/mqiMQCNO.go | 2 +- ibmmq/mqiPCF.go | 2 +- mqmetric/queue.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ibmmq/mqiMQCNO.go b/ibmmq/mqiMQCNO.go index 035b91e..44ee8d5 100644 --- a/ibmmq/mqiMQCNO.go +++ b/ibmmq/mqiMQCNO.go @@ -1,7 +1,7 @@ package ibmmq /* - Copyright (c) IBM Corporation 2016 + Copyright (c) IBM Corporation 2016,2018 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/ibmmq/mqiPCF.go b/ibmmq/mqiPCF.go index 49f0864..d260ca4 100644 --- a/ibmmq/mqiPCF.go +++ b/ibmmq/mqiPCF.go @@ -1,7 +1,7 @@ package ibmmq /* - Copyright (c) IBM Corporation 2016 + Copyright (c) IBM Corporation 2016,2018 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/mqmetric/queue.go b/mqmetric/queue.go index 7657429..3fd7cdc 100644 --- a/mqmetric/queue.go +++ b/mqmetric/queue.go @@ -6,7 +6,7 @@ storage mechanisms including Prometheus and InfluxDB. package mqmetric /* - Copyright (c) IBM Corporation 2016, 2018 + Copyright (c) IBM Corporation 2018 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.