/
qmgr.go
278 lines (225 loc) · 8.66 KB
/
qmgr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/*
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 2018,2020
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 QMSTATUS command to extract metrics
about the MQ queue manager
*/
import (
"github.com/ibm-messaging/mq-golang/v5/ibmmq"
"strings"
"time"
)
const (
ATTR_QMGR_NAME = "name"
ATTR_QMGR_CONNECTION_COUNT = "connection_count"
ATTR_QMGR_CHINIT_STATUS = "channel_initiator_status"
ATTR_QMGR_CMD_SERVER_STATUS = "command_server_status"
ATTR_QMGR_STATUS = "status"
ATTR_QMGR_UPTIME = "uptime"
ATTR_QMGR_MAX_CHANNELS = "max_channels"
ATTR_QMGR_MAX_ACTIVE_CHANNELS = "max_active_channels"
ATTR_QMGR_MAX_TCP_CHANNELS = "max_tcp_channels"
)
/*
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 QueueManagerInitAttributes() {
traceEntry("QueueManagerInitAttributes")
ci := getConnection(GetConnectionKey())
os := &ci.objectStatus[OT_Q_MGR]
st := GetObjectStatus(GetConnectionKey(), OT_Q_MGR)
if os.init {
traceExit("QueueManagerInitAttributes", 1)
return
}
st.Attributes = make(map[string]*StatusAttribute)
attr := ATTR_QMGR_NAME
st.Attributes[attr] = newPseudoStatusAttribute(attr, "Queue Manager Name")
if GetPlatform() != ibmmq.MQPL_ZOS {
attr = ATTR_QMGR_UPTIME
st.Attributes[attr] = newStatusAttribute(attr, "Up time", -1)
// These are the integer status fields that are of interest
attr = ATTR_QMGR_CONNECTION_COUNT
st.Attributes[attr] = newStatusAttribute(attr, "Connection Count", ibmmq.MQIACF_CONNECTION_COUNT)
attr = ATTR_QMGR_CHINIT_STATUS
st.Attributes[attr] = newStatusAttribute(attr, "Channel Initiator Status", ibmmq.MQIACF_CHINIT_STATUS)
attr = ATTR_QMGR_CMD_SERVER_STATUS
st.Attributes[attr] = newStatusAttribute(attr, "Command Server Status", ibmmq.MQIACF_CMD_SERVER_STATUS)
} else {
attr = ATTR_QMGR_MAX_CHANNELS
st.Attributes[attr] = newStatusAttribute(attr, "Max Channels", -1)
attr = ATTR_QMGR_MAX_TCP_CHANNELS
st.Attributes[attr] = newStatusAttribute(attr, "Max TCP Channels", -1)
attr = ATTR_QMGR_MAX_ACTIVE_CHANNELS
st.Attributes[attr] = newStatusAttribute(attr, "Max Active Channels", -1)
}
// The qmgr status is reported to Prometheus with some pseudo-values so we can see if
// we are not actually connected. On other collectors, the whole collection process is
// halted so this would not be reported.
attr = ATTR_QMGR_STATUS
st.Attributes[attr] = newStatusAttribute(attr, "Queue Manager Status", ibmmq.MQIACF_Q_MGR_STATUS)
os.init = true
traceExit("QueueManagerInitAttributes", 0)
}
func CollectQueueManagerStatus() error {
var err error
traceEntry("CollectQueueManagerStatus")
//os := &ci.objectStatus[OT_Q_MGR]
st := GetObjectStatus(GetConnectionKey(), OT_Q_MGR)
QueueManagerInitAttributes()
for k := range st.Attributes {
st.Attributes[k].Values = make(map[string]*StatusValue)
}
// Empty any collected values
if GetPlatform() == ibmmq.MQPL_ZOS {
err = collectQueueManagerAttrs()
} else {
err = collectQueueManagerStatus(ibmmq.MQOT_Q_MGR)
}
traceExitErr("CollectQueueManagerStatus", 0, err)
return err
}
// On z/OS there are a couple of static-ish values that might be helpful.
// They can be obtained via MQINQ and do not need a PCF flow.
// We can't get these on Distributed because equivalents are in qm.ini
func collectQueueManagerAttrs() error {
traceEntry("collectQueueManagerAttrs")
ci := getConnection(GetConnectionKey())
st := GetObjectStatus(GetConnectionKey(), OT_Q_MGR)
selectors := []int32{ibmmq.MQCA_Q_MGR_NAME,
ibmmq.MQIA_ACTIVE_CHANNELS,
ibmmq.MQIA_TCP_CHANNELS,
ibmmq.MQIA_MAX_CHANNELS}
v, err := ci.si.qMgrObject.Inq(selectors)
if err == nil {
maxchls := v[ibmmq.MQIA_MAX_CHANNELS].(int32)
maxact := v[ibmmq.MQIA_ACTIVE_CHANNELS].(int32)
maxtcp := v[ibmmq.MQIA_TCP_CHANNELS].(int32)
key := v[ibmmq.MQCA_Q_MGR_NAME].(string)
st.Attributes[ATTR_QMGR_MAX_ACTIVE_CHANNELS].Values[key] = newStatusValueInt64(int64(maxact))
st.Attributes[ATTR_QMGR_MAX_CHANNELS].Values[key] = newStatusValueInt64(int64(maxchls))
st.Attributes[ATTR_QMGR_MAX_TCP_CHANNELS].Values[key] = newStatusValueInt64(int64(maxtcp))
st.Attributes[ATTR_QMGR_NAME].Values[key] = newStatusValueString(key)
// This pseudo-value will always get filled in for a z/OS qmgr - we know it's running because
// we've been able to connect!
st.Attributes[ATTR_QMGR_STATUS].Values[key] = newStatusValueInt64(int64(ibmmq.MQQMSTA_RUNNING))
}
traceExitErr("collectQueueManagerAttrs", 0, err)
return err
}
// Issue the INQUIRE_Q_MGR_STATUS command for the queue mgr.
// Collect the responses and build up the statistics
func collectQueueManagerStatus(instanceType int32) error {
var err error
traceEntry("collectQueueManagerStatus")
ci := getConnection(GetConnectionKey())
statusClearReplyQ()
putmqmd, pmo, cfh, buf := statusSetCommandHeaders()
// Can allow all the other fields to default
cfh.Command = ibmmq.MQCMD_INQUIRE_Q_MGR_STATUS
// 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 = ci.si.cmdQObj.Put(putmqmd, pmo, buf)
if err != nil {
traceExitErr("collectQueueManagerStatus", 1, err)
return err
}
// Now get the responses - loop until all have been received (one
// per queue) or we run out of time
for allReceived := false; !allReceived; {
cfh, buf, allReceived, err = statusGetReply()
if buf != nil {
parseQMgrData(instanceType, cfh, buf)
}
}
traceExitErr("collectQueueManagerStatus", 0, err)
return err
}
// Given a PCF response message, parse it to extract the desired statistics
func parseQMgrData(instanceType int32, cfh *ibmmq.MQCFH, buf []byte) string {
var elem *ibmmq.PCFParameter
traceEntry("parseQMgrData")
st := GetObjectStatus(GetConnectionKey(), OT_Q_MGR)
qMgrName := ""
key := ""
startTime := ""
startDate := ""
parmAvail := true
bytesRead := 0
offset := 0
datalen := len(buf)
if cfh == nil || cfh.ParameterCount == 0 {
traceExit("parseQMgrData", 1)
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_MGR_NAME:
qMgrName = strings.TrimSpace(elem.String[0])
}
}
// Create a unique key for this instance
key = qMgrName
st.Attributes[ATTR_QMGR_NAME].Values[key] = newStatusValueString(qMgrName)
// 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
}
if !statusGetIntAttributes(GetObjectStatus(GetConnectionKey(), OT_Q_MGR), elem, key) {
switch elem.Parameter {
case ibmmq.MQCACF_Q_MGR_START_TIME:
startTime = strings.TrimSpace(elem.String[0])
case ibmmq.MQCACF_Q_MGR_START_DATE:
startDate = strings.TrimSpace(elem.String[0])
}
}
}
now := time.Now()
st.Attributes[ATTR_QMGR_UPTIME].Values[key] = newStatusValueInt64(statusTimeDiff(now, startDate, startTime))
traceExitF("parseQMgrData", 0, "Key: %s", key)
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 QueueManagerNormalise(attr *StatusAttribute, v int64) float64 {
return statusNormalise(attr, v)
}