MQ PCF value formatting

In MQ V8, a new header file made it easy for C programs to convert MQI numbers into the corresponding string definition. For example, turning 2035 into MQRC_NOT_AUTHORIZED. For formatting PCF-based messages such as events, you can easily convert the names of fields in those events using functions like MQIA_STR or MQCACH_STR. But what about turning the values in those elements into a string? There is a new tool in the MQ Go library to help with MQ PCF value formatting. And it is easy to adopt or port it for other languages too.

The problem

Consider a PCF element where the name/value pair looks like {5,0}. Calling MQIA_STR(5) in C, or MQConstants.lookup(5,"MQIA_.*") in Java will return the attribute name: MQIA_DEF_PERSISTENCE. We know from the context that the name will be in one of the MQIA/MQCA/MQBA ranges. So we can easily try each of these sets or prefixes until we find a match.

The corresponding code to turn the value into a string would be MQPER_STR(0) or MQConstants.lookup(0,"MQPER_.*") to get MQPER_NOT_PERSISTENT. But how do we know that the function or prefix that gets us to the conversion function comes from “MQPER”? The simple answer is that there is no way programmatically to know. There is no direct relationship between an attribute’s name and the corresponding MQI definitions for the value.

The original MQI designers did not consider creating or enforcing a rule. They believed (probably correctly) that a readable constant name was more useful. And they had the constraint of having to live within 31 characters for the definitions. Adding linkage in the names to give a more explicit relationship would likely mean losing characters that make the names memorable and readable.

The old solution

I’ve written code that deals with this several times before. For example as part of SupportPac MS0P I had a Java solution, and the product’s amqsevta.c has a C solution. And other applications had partial decoders, enough to demonstrate the principles. There’s also something similar in the MQ source code itself. The C code in amqsevta.c looks something like

switch (cfh.Type) {
case MQCFT_INTEGER:
  attr = cfin.Parameter;
  val  = cfin.Value;
  switch (attr) {
    case MQIA_DEF_PERSISTENCE:
      fn = MQPER_STR;
      break;
    ...
  }
  valString = fn(val);
...
}

The switch statements have been built by hand, and have to be reviewed at each release. They are also not necessarily complete. In particular, most of these solutions do not call out the attributes needing no conversion. Because the lists are then incomplete, it’s hard to spot when there’s something new that needs to be added.

A quick check of the MQI definitions gives about 750 entries in the MQIA range and its subranges such as MQIACF. While roughly half of these do not need interpretation of the value (MQIA_CURRENT_Q_DEPTH references a number that really is just a number), that still leaves a lot of functions that could be called.

I’ve recently had some MQ developers asking about the best way to handle this problem for various projects. I referred them to these existing solutions, but it got me thinking about creating a better one.

The new solution

The new approach is to create a function or table that handles the mapping between the attributes and their value converter. That function also needs to know which attributes do not actually need a string conversion. And it needs to be kept up-to-date.

I extracted a list of all of the 750 MQIA definitions and used existing material to make an initial version of the map. If amqsevt or MS0P knew about the definition, I used that as the starting point. Various one-off awk or sed or grep scripts pulled out the interesting lines and formatted them. And then I went through the list to validate it line by line.

You can see the final result in the file mqiPCFstr.go. It has a complete map, returning the prefix:

var pcfAttrMap = map[int32]string{
  MQIA_ACCOUNTING_CONN_OVERRIDE:  "MQMON",
  MQIA_ACCOUNTING_INTERVAL:       "",
  MQIA_ACTIVE_CHANNELS:           "",
  MQIA_ACTIVITY_CONN_OVERRIDE:    "MQMON",
  MQIA_ACTIVITY_RECORDING:        "MQRECORDING",
  MQIA_ACTIVITY_TRACE:            "MQMON",
  MQIA_ADOPT_CONTEXT:             "MQADPCTX",
  MQIA_ADOPTNEWMCA_CHECK:         "MQADOPT_CHECK",
...

An empty string implies that no conversion is required. The value is just a number.

This map was still hand-validated. Which was tedious and took time. But it was hopefully a one-time cost. The release process for the Go libraries now includes a check for any newly-defined attributes as all of the current attributes are in the map – not just those that need a conversion. That check will prompt an update to the map. The update has to be done manually, as we still can’t know what the new mapping function is. But there ought not to be a huge number of additional attributes to verify at each release. So this becomes a sustainable and maintainable piece of code.

The Go file also includes a couple of helper functions to navigate the map: PCFAttrToPrefix and PCFValueToStringlook in the map and return either the mapping prefix or the converted string itself.

Other languages

Only the Go library currently provides this mapping. But it is fairly easy to convert into other languages. One of MQ’s Java components has already adopted it, doing a mechanical conversion into Java with a HashMap initialisation. Something like an awk script can trivially take the relevant lines from Go and reformat into the corresponding syntax for a different environment.

And I can even see how I might rewrite it for the C event formatter if it was useful. Even though C does not have an in-built Map type, the fact that the key is an integer means that we could generate a switch/case block fairly easily and efficiently.

Samples

I’ve provided two programs that show how to use this new map.

amqspcf.go

In the mq-golang repository, the already-available PCF sample program has been updated.

The previous version knows explicitly about the MQIA_Q_TYPEdefinition, but no others:

Got message of format MQADMIN and length 1752:
Name: MQCA_Q_NAME                       Value: DEV.QUEUE.1
Name: MQIA_Q_TYPE                       Value: MQQT_LOCAL
Name: MQIA_ACCOUNTING_Q                 Value: -3
Name: MQCA_ALTERATION_DATE              Value: 2024-12-10
Name: MQCA_ALTERATION_TIME              Value: 11.30.47
Name: MQCA_BACKOUT_REQ_Q_NAME           Value:
Name: MQIA_BACKOUT_THRESHOLD            Value: 0
Name: MQIA_CAP_EXPIRY                   Value: -1
...

The updated version can format all of the integer values:

Got message of format MQADMIN and length 1752:
Name: MQCA_Q_NAME                       Value: DEV.QUEUE.1
Name: MQIA_Q_TYPE                       Value: MQQT_LOCAL
Name: MQIA_ACCOUNTING_Q                 Value: MQMON_Q_MGR
Name: MQCA_ALTERATION_DATE              Value: 2024-12-10
Name: MQCA_ALTERATION_TIME              Value: 11.30.47
Name: MQCA_BACKOUT_REQ_Q_NAME           Value:
Name: MQIA_BACKOUT_THRESHOLD            Value: 0
Name: MQIA_CAP_EXPIRY                   Value: MQCEX_NOLIMIT
...

amqsevtg

A bigger test that helped verify the processing is one that looks at MQ’s Event messages. I started writing a test program that could format all of the different event types, as those cover just about all of the PCF attributes. As I expanded the scope of the testing, I found that I essentially had a replica of the amqsevt product sample.

And having done that, I decided to continue some of the OpenTelemetry integration activity that I’ve covered in other recent articles here. The program has a feature to output the formatted events directly to an OTel backend instead of going via a file or named pipe intermediate route. While not part of the PCF processing itself, it was interesting to see just how different OTel’s approach is for APIs to capture logging, compared to metrics and traces.

Because this program turned out to be somewhat larger than originally intended, I’ve put it into the mq-metrics-samples repository instead of the core Go repository’s samples. Take a look at the README file to see the options and how it differs from the product amqsevt program. But as a demonstration, I can run:

go run . -e localhost:4317 -i -w 1 -m QM1

and in a locally-running OTel Collector, I see output like:

LogRecord #6
ObservedTimestamp: 2025-01-13 11:28:20.433205975 +0000 UTC
Timestamp: 2025-01-13 11:28:20.433204717 +0000 UTC
SeverityText:
SeverityNumber: Info(9)
Body: Str({"eventSource":{"objectName":"SYSTEM.ADMIN.CONFIG.EVENT","objectType":"Queue","queueManager":"QM1"},"eventType":{"name":"Config Event","value":43},"eventReason":{"name":"Config Change Object","value":2368},"eventCreation":{"timeStamp":"2025-01-13T11:20:37.2Z","epoch":1736767237},"correlationID":"414d5120514d3120202020202020202050c980675d0f9040","objectState":"After Change","eventData":{"accountingQ":"Queue Mgr","alterationDate":"2025-01-13","alterationTime":"11.20.36","backoutReqQName":"","backoutThreshold":0,"capExpiry":"Nolimit","clusChlName":"","clusterName":"","clusterNamelist":"","clwlQPriority":0,"clwlQRank":0,"clwlUseq":"Useq As Queue Mgr","creationDate":"2022-02-22","creationTime":"12.19.26","custom":"","defBind":"Bind On Open","defInputOpenOption":"Input Shared","defPersistence":"Not Persistent","defPriority":0,"defPutResponseType":"Sync Response","defReadAhead":"No","definitionType":"Predefined","distLists":"Not Supported","eventOrigin":"Mqset","eventQMgr":"QM1","eventUserId":"metaylor","hardenGetBackout":"Backout Hardened","inhibitGet":"Get Inhibited","inhibitPut":"Put Allowed","initiationQName":"","maxMsgLength":4194304,"maxQDepth":5000,"maxQFileSize":"Default","mediaImageRecoverQ":"As Queue Mgr","monitoringQ":"Queue Mgr","msgDeliverySequence":"Priority","npmClass":"Class Normal","objectType":"Queue","processName":"","propertyControl":"Compatibility","qDepthHighEvent":"Disabled","qDepthHighLimit":80,"qDepthLowEvent":"Disabled","qDepthLowLimit":20,"qDepthMaxEvent":"Enabled","qDesc":"","qName":"QM2.RETRY","qServiceInterval":999999999,"qServiceIntervalEvent":"None","qType":"Local","retentionInterval":999999999,"scope":"Queue Mgr","shareability":"Shareable","statisticsQ":"Queue Mgr","streamQueueName":"","streamQueueQos":"Best Effort","triggerControl":"Off","triggerData":"","triggerDepth":1,"triggerMsgPriority":0,"triggerType":"First","usage":"Transmission"}})
Attributes:
     -> eventCreation: Str(2025-01-13T11:20:37.2Z)

You can see the JSON body of the event with all the formatted attributes.

There is also a test script to drive the creation of various kinds of events. You would need to modify the script for your own environment, but it shows an outline of how to work with these events and a consuming program.

Conclusion

This problem exists for many MQ monitoring applications. Maybe, one day, the product itself will include an externalised version of this map. But until it does, I hope this is a useful piece of work.

This post was last updated on February 27th, 2025 at 08:08 pm

Leave a Reply

Your email address will not be published. Required fields are marked *