diff --git a/ibm-mq-metrics/docs/metrics.md b/ibm-mq-metrics/docs/metrics.md index 63a71e3f2..2cfe9047c 100644 --- a/ibm-mq-metrics/docs/metrics.md +++ b/ibm-mq-metrics/docs/metrics.md @@ -799,3 +799,19 @@ | Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | |---|---|---|---|---|---| | `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.connection.errors` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.connection.errors` | Counter | `{errors}` | Number of connection errors | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.connection.errors` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `error.code` | string | The reason code associated with an error | `2038`; `2543`; `2009` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | diff --git a/ibm-mq-metrics/model/attributes.yaml b/ibm-mq-metrics/model/attributes.yaml index ef7ffdafd..4e59f631b 100644 --- a/ibm-mq-metrics/model/attributes.yaml +++ b/ibm-mq-metrics/model/attributes.yaml @@ -73,3 +73,9 @@ groups: note: This is duplicated from otel semantic-conventions. stability: development examples: [ "Wordle", "JMSService" ] + - id: error.code + type: string + brief: > + The reason code associated with an error + stability: development + examples: [ "2038", "2543", "2009" ] diff --git a/ibm-mq-metrics/model/metrics.yaml b/ibm-mq-metrics/model/metrics.yaml index cdc20456a..9e4e72fee 100644 --- a/ibm-mq-metrics/model/metrics.yaml +++ b/ibm-mq-metrics/model/metrics.yaml @@ -609,3 +609,15 @@ groups: attributes: - ref: ibm.mq.queue.manager requirement_level: required + - id: ibm.mq.connection.errors + type: metric + metric_name: ibm.mq.connection.errors + stability: development + brief: "Number of connection errors" + instrument: counter + unit: "{errors}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: error.code + requirement_level: required diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java index 9f0a64f21..1ab70f192 100644 --- a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java @@ -18,12 +18,15 @@ import com.ibm.mq.headers.pcf.PCFException; import com.ibm.mq.headers.pcf.PCFMessage; import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.ibm.mq.config.QueueManager; import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; import io.opentelemetry.ibm.mq.opentelemetry.Main; import io.opentelemetry.ibm.mq.util.WmqUtil; +import io.opentelemetry.sdk.metrics.data.LongPointData; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.SumData; import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; import java.io.File; import java.net.URISyntaxException; @@ -281,4 +284,34 @@ void test_otlphttp() throws Exception { // reads a value from the heartbeat gauge assertThat(metricNames).contains("ibm.mq.heartbeat"); } + + @Test + void test_bad_connection() throws Exception { + logger.info("\n\n\n\n\n\nRunning test: test_bad_connection"); + String configFile = getConfigFile("conf/test-bad-config.yml"); + + ConfigWrapper config = ConfigWrapper.parse(configFile); + Meter meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + TestWMQMonitor monitor = new TestWMQMonitor(config, meter, service); + monitor.runTest(); + + List data = otelTesting.getMetrics(); + + assertThat(data).isNotEmpty(); + assertThat(data).hasSize(2); + + SumData connectionErrors = null; + for (MetricData metricData : data) { + if ("ibm.mq.connection.errors".equals(metricData.getName())) { + connectionErrors = (SumData) metricData.getData(); + } + } + + assertThat(connectionErrors).isNotNull(); + + LongPointData metricPoint = connectionErrors.getPoints().iterator().next(); + String value = metricPoint.getAttributes().get(AttributeKey.stringKey("error.code")); + + assertThat(value).isEqualTo("2538"); + } } diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml new file mode 100644 index 000000000..e839e54ed --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml @@ -0,0 +1,302 @@ +queueManagers: + - name: "QM1" + host: "localhost" + port: 1417 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Client" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + channelName: DEV.ADMIN.SVRCONN + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + username: "admin" + password: "passw0rd" + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +mqMetrics: + # This Object will extract queue manager metrics + - metricsType: "queueMgrMetrics" + metrics: + include: + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_Q_MGR_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + - ConnectionCount: + alias: "ConnectionCount" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CONNECTION_COUNT" + + - ReusableLogSize: + alias: "Reusable Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_REUSABLE_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - RestartLogSize: + alias: "Restart Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_RESTART_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - ArchiveLogSize: + alias: "Archive Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_ARCHIVE_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - StatisticsInterval: + alias: "Statistics Interval" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_STATISTICS_INTERVAL" + ibmCommand: "MQCMD_INQUIRE_Q_MGR" + + # This Object will extract queue metrics + - metricsType: "queueMetrics" + metrics: + include: + - MaxQueueDepth: + alias: "Max Queue Depth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MAX_Q_DEPTH" + ibmCommand: "MQCMD_INQUIRE_Q" + + - CurrentQueueDepth: + alias: "Current Queue Depth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_CURRENT_Q_DEPTH" + ibmCommand: "MQCMD_INQUIRE_Q" + + - CurrentQueueFileSize: + alias: "Current queue file size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CUR_Q_FILE_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - MaxQueueFileSize: + alias: "Current maximum queue file size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CUR_MAX_FILE_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - OpenInputCount: + alias: "Open Input Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_OPEN_INPUT_COUNT" + ibmCommand: "MQCMD_INQUIRE_Q" + + - OpenOutputCount: + alias: "Open Output Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_OPEN_OUTPUT_COUNT" + ibmCommand: "MQCMD_INQUIRE_Q" + + - OldestMsgAge: + alias: "OldestMsgAge" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_OLDEST_MSG_AGE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "CURRENT" + clusterRollUpType: "INDIVIDUAL" + + - UncommittedMsgs: + alias: "UncommittedMsgs" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_UNCOMMITTED_MSGS" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - OnQTime: + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_Q_TIME_INDICATOR" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "CURRENT" + clusterRollUpType: "INDIVIDUAL" + + - HighQDepth: + alias: "HighQDepth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_HIGH_Q_DEPTH" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - MsgDeqCount: + alias: "MsgDeqCount" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MSG_DEQ_COUNT" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - MsgEnqCount: + alias: "MsgEnqCount" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MSG_ENQ_COUNT" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - UncommittedMsgs: + alias: "Uncommitted Messages" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_UNCOMMITTED_MSGS" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - ServiceInterval: + alias: "Service Interval" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_Q_SERVICE_INTERVAL" + ibmCommand: "MQCMD_INQUIRE_Q" + + - ServiceIntervalEvent: + alias: "Service Interval Event" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT" + ibmCommand: "MQCMD_INQUIRE_Q" + + # This Object will extract channel metrics + - metricsType: "channelMetrics" + metrics: + include: + - Messages: + alias: "Messages" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_CHANNEL_STATUS" #http://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.dev.doc/q090880_.htm + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - ByteSent: + alias: "Byte Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BYTES_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - ByteReceived: + alias: "Byte Received" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BYTES_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - BuffersSent: + alias: "Buffers Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BUFFERS_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - BuffersReceived: + alias: "Buffers Received" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BUFFERS_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - CurrentSharingConversations: + alias: "Current Sharing Conversations" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_CURRENT_SHARING_CONVS" + + - MaxSharingConversations: + alias: "Max Sharing Conversations" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_SHARING_CONVS" + + - MaxInstances: + alias: "Max Instances" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_INSTANCES" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MaxInstancesPerClient: + alias: "Max Instances per Client" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgRetryCount: + alias: "Message Retry Count" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MR_COUNT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgsReceived: + alias: "Message Received Count" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgsSent: + alias: "Message Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - metricsType: "listenerMetrics" + metrics: + include: + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_LISTENER_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + + # This Object will extract topic metrics + - metricsType: "topicMetrics" + metrics: + include: + - PublishCount: + alias: "Publish Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_PUB_COUNT" + ibmCommand: "MQCMD_INQUIRE_TOPIC_STATUS" + - SubscriptionCount: + alias: "Subscription Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_SUB_COUNT" + ibmCommand: "MQCMD_INQUIRE_TOPIC_STATUS" + + # Sets up reading configuration events from the configuration queue. + - metricsType: "configurationMetrics" + metrics: + include: + - MaxHandles: + alias: "Max Handles" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MAX_HANDLES" + +#Run it as a scheduled task instead of running every minute. +#If you want to run this every minute, comment this out +#taskSchedule: +# numberOfThreads: 1 +# taskDelaySeconds: 300 + + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + trustStoreEncryptedPassword: "" + + keyStorePath: "" + keyStorePassword: "" + keyStoreEncryptedPassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: http://0.0.0.0:4318 diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java index 7eaa2d8c0..10a28eb9e 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -5,15 +5,19 @@ package io.opentelemetry.ibm.mq; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.ERROR_CODE; import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQException; import com.ibm.mq.MQQueueManager; import com.ibm.mq.headers.pcf.PCFMessageAgent; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongGauge; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.Metrics; import io.opentelemetry.ibm.mq.metrics.MetricsConfig; import io.opentelemetry.ibm.mq.metricscollector.ChannelMetricsCollector; import io.opentelemetry.ibm.mq.metricscollector.InquireChannelCmdCollector; @@ -45,6 +49,7 @@ public class WmqMonitor { private final List queueManagers; private final List> jobs = new ArrayList<>(); + private final LongCounter errorCodesCounter; private final LongGauge heartbeatGauge; private final ExecutorService threadPool; private final MetricsConfig metricsConfig; @@ -67,6 +72,7 @@ public WmqMonitor(ConfigWrapper config, ExecutorService threadPool, Meter meter) this.metricsConfig = new MetricsConfig(config); this.heartbeatGauge = meter.gaugeBuilder("ibm.mq.heartbeat").setUnit("1").ofLongs().build(); + this.errorCodesCounter = Metrics.createIbmMqConnectionErrors(meter); this.threadPool = threadPool; jobs.add(new QueueManagerMetricsCollector(meter)); @@ -106,6 +112,12 @@ public void run(QueueManager queueManager) { Thread.currentThread().getName(), e.getMessage(), e); + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + String errorCode = String.valueOf(mqe.getReason()); + errorCodesCounter.add( + 1, Attributes.of(IBM_MQ_QUEUE_MANAGER, queueManagerName, ERROR_CODE, errorCode)); + } } finally { if (this.metricsConfig.isIbmMqHeartbeatEnabled()) { heartbeatGauge.set( diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java index 6caf87c48..fd84b6871 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java @@ -47,5 +47,8 @@ public final class IbmMqAttributes { /** Logical name of the service. */ public static final AttributeKey SERVICE_NAME = stringKey("service.name"); + /** The reason code associated with an error */ + public static final AttributeKey ERROR_CODE = stringKey("error.code"); + private IbmMqAttributes() {} } diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java index c54c9a431..3698c87c9 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java @@ -421,4 +421,12 @@ public static LongGauge createIbmMqManagerMaxHandles(Meter meter) { .setDescription("Max open handles") .build(); } + + public static LongCounter createIbmMqConnectionErrors(Meter meter) { + return meter + .counterBuilder("ibm.mq.connection.errors") + .setUnit("{errors}") + .setDescription("Number of connection errors") + .build(); + } } diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java index c172cd532..acc975f8f 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java @@ -199,6 +199,10 @@ public boolean isIbmMqManagerMaxHandlesEnabled() { return isEnabled("ibm.mq.manager.max.handles"); } + public boolean isIbmMqConnectionErrorsEnabled() { + return isEnabled("ibm.mq.connection.errors"); + } + private boolean isEnabled(String key) { Object metricInfo = config.get(key); if (!(metricInfo instanceof Map)) {