diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 1e4c90e14..6b74234ad 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -90,3 +90,6 @@ components: opamp-client: - LikeTheSalad - jackshirazi + ibm-mq-metrics: + - breedx-splk + - atoulme diff --git a/README.md b/README.md index 080bd8804..c42ea7e27 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ feature or via instrumentation, this project is hopefully for you. | alpha | [GCP Authentication Extension](./gcp-auth-extension/README.md) | | beta | [GCP Resources](./gcp-resources/README.md) | | beta | [Inferred Spans](./inferred-spans/README.md) | +| alpha | [IBM MQ Metrics](./ibm-mq-metrics/README.md) | | alpha | [JFR Connection](./jfr-connection/README.md) | | alpha | [JFR Events](./jfr-events/README.md) | | alpha | [JMX Metric Gatherer](./jmx-metrics/README.md) | diff --git a/ibm-mq-metrics/Makefile b/ibm-mq-metrics/Makefile new file mode 100644 index 000000000..d6ad8e42a --- /dev/null +++ b/ibm-mq-metrics/Makefile @@ -0,0 +1,84 @@ +# From where to resolve the containers (e.g. "otel/weaver"). +WEAVER_CONTAINER_REPOSITORY=docker.io +# Versioned, non-qualified references to containers used in this Makefile. +# These are parsed from dependencies.Dockerfile so dependabot will autoupdate +# the versions of docker files we use. +VERSIONED_WEAVER_CONTAINER_NO_REPO=$(shell cat weaver.Dockerfile | awk '$$4=="weaver" {print $$2}') +# Versioned, non-qualified references to containers used in this Makefile. +WEAVER_CONTAINER=$(WEAVER_CONTAINER_REPOSITORY)/$(VERSIONED_WEAVER_CONTAINER_NO_REPO) + +# Next - we want to run docker as our local file user, so generated code is not +# owned by root, and we don't give unnecessary access. +# +# Determine if "docker" is actually podman +DOCKER_VERSION_OUTPUT := $(shell docker --version 2>&1) +DOCKER_IS_PODMAN := $(shell echo $(DOCKER_VERSION_OUTPUT) | grep -c podman) +ifeq ($(DOCKER_IS_PODMAN),0) + DOCKER_COMMAND := docker +else + DOCKER_COMMAND := podman +endif +DOCKER_RUN=$(DOCKER_COMMAND) run +DOCKER_USER=$(shell id -u):$(shell id -g) +DOCKER_USER_IS_HOST_USER_ARG=-u $(DOCKER_USER) +ifeq ($(DOCKER_COMMAND),podman) + # On podman, additional arguments are needed to make "-u" work + # correctly with the host user ID and host group ID. + # + # Error: OCI runtime error: crun: setgroups: Invalid argument + DOCKER_USER_IS_HOST_USER_ARG=--userns=keep-id -u $(DOCKER_USER) +endif + +.PHONY: generate-docs +generate-docs: + mkdir -p docs + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/docs,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + markdown \ + --future \ + /home/weaver/target + +.PHONY: check +check: + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/docs,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry check \ + --registry=/home/weaver/model + +.PHONY: generate-java +generate-java: + mkdir -p src/main/java/io/opentelemetry/ibm/mq/metrics + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/src/main/java/io/opentelemetry/ibm/mq/metrics,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + java \ + --future \ + /home/weaver/target + +.PHONY: generate-yaml +generate-yaml: + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + yaml \ + --future \ + /home/weaver/target + +.PHONY: generate +generate: generate-docs generate-yaml generate-java diff --git a/ibm-mq-metrics/README.md b/ibm-mq-metrics/README.md new file mode 100644 index 000000000..5c75acd32 --- /dev/null +++ b/ibm-mq-metrics/README.md @@ -0,0 +1,228 @@ +# IBM MQ Metrics + +:warning: This software is under development. + +## Use case + +IBM MQ, formerly known as WebSphere MQ (message queue) series, is an IBM software for +program-to-program messaging across multiple platforms. + +The IBM MQ metrics utility here can monitor multiple queues managers and their resources, +namely queues, topics, channels and listeners The metrics are extracted out using the +[PCF command messages](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm). + +The metrics for queue manager, queue, topic, channel and listener can be configured. + +The MQ Monitor is compatible with IBM MQ version 7.x, 8.x and 9.x. + +## Prerequisites + +This software requires compilation with Java 11. +It targets language level 8 and outputs java 8 class files. + +The extension has a dependency on the following jar's depending on IBM MQ version: + +* v8.0.0 and above + +``` +com.ibm.mq.allclient.jar +``` + +* For other versions + +``` +com.ibm.mq.commonservices.jar +com.ibm.mq.jar +com.ibm.mq.jmqi.jar +com.ibm.mq.headers.jar +com.ibm.mq.pcf.jar +dhbcore.jar +connector.jar +``` + +These jar files are typically found in ```/opt/mqm/java/lib``` on a UNIX server but may be +found in an alternate location depending upon your environment. + +In case of **CLIENT** transport type, IBM MQ Client must be installed to get the MQ jars. +[The IBM MQ Client jars can be downloaded here](https://developer.ibm.com/messaging/mq-downloads/). + +### MQ monitoring configuration + +This software reads events from event queues associated with the queue manager: + +* `SYSTEM.ADMIN.PERFM.EVENT`: Performance events, such as low, high, and full queue depth events. +* `SYSTEM.ADMIN.QMGR.EVENT`: Authority events +* `SYSTEM.ADMIN.CONFIG.EVENT`: Configuration events + +Please turn on those events to take advantage of this monitoring. + +## Build + +Build the package with: + +```shell +cd ibm-mq-metrics +../gradlew shadowJar +``` + +Note: Due to restrictive licensing, this uber-jar (fat-jar) does not include the IBM client jar. + +## Run + +Run the standalone jar alongside the IBM jar: + +```shell +cd ibm-mq-metrics +java \ + -Djavax.net.ssl.keyStore=key.jks \ + -Djavax.net.ssl.keyStorePassword= \ + -Djavax.net.ssl.trustStore=key.jks \ + -Djavax.net.ssl.trustStorePassword= \ + -cp build/libs/opentelemetry-ibm-mq-monitoring--all.jar:lib/com.ibm.mq.allclient.jar \ + io.opentelemetry.ibm.mq.opentelemetry.Main \ + ./my-config.yml +``` + +## Connection + +There are two transport modes in which this extension can be run: + +* **Binding** : Requires WMQ Extension to be deployed in machine agent on the same machine where + WMQ server is installed. +* **Client** : In this mode, the WMQ extension is installed on a different host than the IBM MQ + server. Please install the [IBM MQ Client](https://developer.ibm.com/messaging/mq-downloads/) + for this mode to get the necessary jars as mentioned previously. + +If this extension is configured for **CLIENT** transport type + +1. Please make sure the MQ's host and port is accessible. +2. Credentials of user with correct access rights would be needed in config.yml. +3. If the hosting OS for IBM MQ is Windows, Windows user credentials will be needed. + +If you are in **Bindings** mode, please make sure to start the MA process under a user which has +the following permissions on the broker. Similarly, for **Client** mode, please provide the user +credentials in config.yml which have permissions listed below. + +The user connecting to the queueManager should have the inquire, get, put (since PCF responses +cause dynamic queues to be created) permissions. For metrics that execute MQCMD_RESET_Q_STATS +command, chg permission is needed. + +### SSL Support + +_Note: The following is only needed for versions of Java 8 before 8u161._ + +1. Configure the IBM SSL Cipher Suite in the config.yml. + Note that, to use some CipherSuites the unrestricted policy needs to be configured in JRE. + Please visit [this link](http://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/sdkpolicyfiles.html) + for more details. For Oracle JRE, please update with [JCE Unlimited Strength Jurisdiction Policy](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). + The download includes a readme file with instructions on how to apply these files to JRE. + +2. Please add the following JVM arguments to the MA start up command or script. + + ```-Dcom.ibm.mq.cfg.useIBMCipherMappings=false``` (If you are using IBM Cipher Suites, set the + flag to true. Please visit [this link](http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm) for more details. + ) +3. To configure SSL, the MA's trust store and keystore needs to be setup with the JKS filepath. + They can be passed either as Machine Agent JVM arguments or configured in config.yml (sslConnection)
+ + a. Machine Agent JVM arguments as follows: + + ```-Djavax.net.ssl.trustStore=```
+ ```-Djavax.net.ssl.trustStorePassword=```
+ ```-Djavax.net.ssl.keyStore=```
+ ```-Djavax.net.ssl.keyStorePassword=```
+ + b. sslConnection in config.yml, configure the trustStorePassword. Same holds for keyStore configuration as well. + + ``` + sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + ``` + +## Configuration + +**Note** : Please make sure to not use tab (\t) while editing yaml files. You may want to validate +the yaml file using a [yaml validator](https://jsonformatter.org/yaml-validator). Configure the monitor by copying and editing the +config.yml file in src/main/resources/config.yml. + +1. Configure the queueManagers with appropriate fields and filters. You can configure multiple + queue managers in one configuration file. +2. To run the extension at a frequency > 1 minute, please configure the taskSchedule section. + Refer to the [Task Schedule](https://community.appdynamics.com/t5/Knowledge-Base/Task-Schedule-for-Extensions/ta-p/35414) doc for details. + +### Monitoring Workings - Internals + +This software extracts metrics through [PCF framework](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q019990_.htm). +[A complete list of PCF commands are listed here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q086870_.htm). +Each queue manager has an administration queue with a standard queue name and +the extension sends PCF command messages to that queue. On Windows and Unix platforms, the PCF +commands are sent is always sent to the SYSTEM.ADMIN.COMMAND.QUEUE queue. +[More details mentioned here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm) + +By default, the PCF responses are sent to the SYSTEM.DEFAULT.MODEL.QUEUE. Using this queue causes +a temporary dynamic queue to be created. You can override the default here by using the +`modelQueueName` and `replyQueuePrefix` fields in the config.yml. +[More details mentioned here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm) + +## Metrics + +See [docs/metrics.md](docs/metrics.md). + +## Troubleshooting + +1. Error `Completion Code '2', Reason '2495'` + Normally this error occurs if the environment variables are not set up correctly for this extension to work MQ in Bindings Mode. + + If you are seeing `Failed to load the WebSphere MQ native JNI library: 'mqjbnd'`, please add the following jvm argument when starting the MA. + + -Djava.library.path=\ For eg. on Unix it could -Djava.library.path=/opt/mqm/java/lib64 for 64-bit or -Djava.library.path=/opt/mqm/java/lib for 32-bit OS + + Sometimes you also have run the setmqenv script before using the above jvm argument to start the machine agent. + + . /opt/mqm/bin/setmqenv -s + + For more details, please check this [doc](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.1.0/com.ibm.mq.doc/zr00610_.htm) + + This might occur due to various reasons ranging from incorrect installation to applying + IBM Fix Packs, but most of the time it happens when you are trying to connect in `Bindings` + mode and machine agent is not on the same machine on which WMQ server is running. If you want + to connect to WMQ server from a remote machine then connect using `Client` mode. + + Another way to get around this issue is to avoid using the Bindings mode. Connect using CLIENT + transport type from a remote box. + +2. Error `Completion Code '2', Reason '2035'` + This could happen for various reasons but for most of the cases, for **Client** mode the + user specified in config.yml is not authorized to access the queue manager. Also sometimes + even if userid and password are correct, channel auth (CHLAUTH) for that queue manager blocks + traffics from other ips, you need to contact admin to provide you access to the queue manager. + For Bindings mode, please make sure that the MA is owned by a mqm user. + +3. `MQJE001: Completion Code '2', Reason '2195'` + This could happen in **Client** mode. Please make sure that the IBM MQ dependency jars are correctly referenced in classpath of monitor.xml + +4. `MQJE001: Completion Code '2', Reason '2400'` + This could happen if unsupported cipherSuite is provided or JRE not having/enabled unlimited jurisdiction policy files. Please check SSL Support section. + +5. If you are seeing `NoClassDefFoundError` or `ClassNotFoundException` error for any of the MQ dependency even after providing correct path in monitor.xml, then you can also try copying all the required jars in WMQMonitor (MAHome/monitors/WMQMonitor) folder and provide classpath in monitor.xml like below + + ``` + opentelemetry-ibm-mq-monitoring--all.jar;com.ibm.mq.allclient.jar + ``` + + OR + + ``` + opentelemetry-ibm-mq-monitoring--all.jar;com.ibm.mq.jar;com.ibm.mq.jmqi.jar;com.ibm.mq.commonservices.jar;com.ibm.mq.headers.jar;com.ibm.mq.pcf.jar;connector.jar;dhbcore.jar + ``` + +## Component Owners + +- [Antoine Toulme Sharma](https://github.com/atoulme), Splunk +- [Jason Plumb](https://github.com/breedx-splk), Splunk + +Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts new file mode 100644 index 000000000..00a2c1988 --- /dev/null +++ b/ibm-mq-metrics/build.gradle.kts @@ -0,0 +1,79 @@ +plugins { + application + id("com.gradleup.shadow") + id("otel.java-conventions") + id("otel.publish-conventions") +} + +description = "IBM-MQ metrics" +otelJava.moduleName.set("io.opentelemetry.contrib.ibm-mq-metrics") +application.mainClass.set("io.opentelemetry.ibm.mq.opentelemetry.Main") + +sourceSets { + create("integrationTest") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + } +} + +val integrationTestImplementation by configurations.getting { + extendsFrom(configurations.implementation.get()) +} +val integrationTestRuntimeOnly by configurations.getting + +configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) + +val ibmClientJar: Configuration by configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +dependencies { + api("com.google.code.findbugs:jsr305:3.0.2") + api("io.swagger:swagger-annotations:1.6.16") + api("org.jetbrains:annotations:26.0.2") + api("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") + api("org.yaml:snakeyaml:2.4") + api("com.fasterxml.jackson.core:jackson-databind:2.19.0") + api("io.opentelemetry:opentelemetry-sdk") + api("io.opentelemetry:opentelemetry-exporter-otlp") + api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + api("org.slf4j:slf4j-api:2.0.7") + testImplementation("com.google.guava:guava") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + integrationTestImplementation("org.assertj:assertj-core:3.27.3") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") + integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.2.0") + integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") + integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") + ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") { + artifact { + name = "com.ibm.mq.allclient" + extension = "jar" + } + isTransitive = false + } +} + +tasks.shadowJar { + dependencies { + exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1")) + } +} + +val integrationTest = tasks.register("integrationTest") { + description = "Runs integration tests." + group = "verification" + + testClassesDirs = sourceSets["integrationTest"].output.classesDirs + classpath = sourceSets["integrationTest"].runtimeClasspath + shouldRunAfter("test") + + useJUnitPlatform() + + testLogging { + events("passed") + } +} diff --git a/ibm-mq-metrics/docs/metrics.md b/ibm-mq-metrics/docs/metrics.md new file mode 100644 index 000000000..63a71e3f2 --- /dev/null +++ b/ibm-mq-metrics/docs/metrics.md @@ -0,0 +1,801 @@ +# Produced Metrics + + +## Metric `ibm.mq.message.retry.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.retry.count` | Gauge | `{message}` | Number of message retries | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.retry.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `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) | + + + +## Metric `ibm.mq.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.status` | Gauge | `1` | Channel status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.status` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.max.sharing.conversations` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.sharing.conversations` | Gauge | `{conversation}` | Maximum number of conversations permitted on this channel instance. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.sharing.conversations` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.current.sharing.conversations` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.sharing.conversations` | Gauge | `{conversation}` | Current number of conversations permitted on this channel instance. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.sharing.conversations` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.byte.received` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.byte.received` | Gauge | `By` | Number of bytes received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.byte.received` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.byte.sent` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.byte.sent` | Gauge | `By` | Number of bytes sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.byte.sent` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.buffers.received` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.buffers.received` | Gauge | `{buffer}` | Buffers received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.buffers.received` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.buffers.sent` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.buffers.sent` | Gauge | `{buffer}` | Buffers sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.buffers.sent` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.message.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.count` | Gauge | `{message}` | Message count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `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) | + + + +## Metric `ibm.mq.open.input.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.open.input.count` | Gauge | `{application}` | Count of applications sending messages to the queue | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.open.input.count` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [1] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[1] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.open.output.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.open.output.count` | Gauge | `{application}` | Count of applications consuming messages from the queue | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.open.output.count` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [2] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[2] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.high.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.high.queue.depth` | Gauge | `{percent}` | The current high queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.high.queue.depth` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [3] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[3] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.service.interval` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.service.interval` | Gauge | `{percent}` | The queue service interval | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.service.interval` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [4] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[4] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.full.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.full.event` | Counter | `{event}` | The number of full queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.full.event` Attributes + +| 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) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [5] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[5] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.high.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.high.event` | Counter | `{event}` | The number of high queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.high.event` Attributes + +| 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) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [6] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[6] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.low.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.low.event` | Counter | `{event}` | The number of low queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.low.event` Attributes + +| 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) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [7] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[7] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.uncommitted.messages` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.uncommitted.messages` | Gauge | `{message}` | Number of uncommitted messages | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.uncommitted.messages` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [8] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[8] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.oldest.msg.age` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.oldest.msg.age` | Gauge | `microseconds` | Queue message oldest age | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.oldest.msg.age` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [9] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[9] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.current.max.queue.filesize` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.max.queue.filesize` | Gauge | `By` | Current maximum queue file size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.max.queue.filesize` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [10] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[10] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.current.queue.filesize` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.queue.filesize` | Gauge | `By` | Current queue file size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.queue.filesize` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [11] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[11] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.instances.per.client` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.instances.per.client` | Gauge | `{instance}` | Instances per client | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.instances.per.client` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [12] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[12] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.deq.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.deq.count` | Gauge | `{message}` | Message dequeue count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.deq.count` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [13] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[13] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.enq.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.enq.count` | Gauge | `{message}` | Message enqueue count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.enq.count` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [14] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[14] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth` | Gauge | `{message}` | Current queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [15] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[15] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.service.interval.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.service.interval.event` | Gauge | `1` | Queue service interval event | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.service.interval.event` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [16] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[16] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.reusable.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.reusable.log.size` | Gauge | `By` | The amount of space occupied, in megabytes, by log extents available to be reused. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.reusable.log.size` Attributes + +| 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.manager.active.channels` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.active.channels` | Gauge | `{channel}` | The queue manager active maximum channels limit | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.active.channels` Attributes + +| 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.restart.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.restart.log.size` | Gauge | `By` | Size of the log data required for restart recovery in megabytes. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.restart.log.size` Attributes + +| 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.max.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.queue.depth` | Gauge | `{message}` | Maximum queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.queue.depth` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [17] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[17] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.onqtime.short_period` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.onqtime.short_period` | Gauge | `microseconds` | Amount of time, in microseconds, that a message spent on the queue, over a short period | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.onqtime.short_period` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [18] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[18] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.onqtime.long_period` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.onqtime.long_period` | Gauge | `microseconds` | Amount of time, in microseconds, that a message spent on the queue, over a longer period | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.onqtime.long_period` Attributes + +| 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) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [19] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[19] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.received.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.received.count` | Gauge | `{message}` | Number of messages received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.received.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `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) | + + + +## Metric `ibm.mq.message.sent.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.sent.count` | Gauge | `{message}` | Number of messages sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.sent.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `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) | + + + +## Metric `ibm.mq.max.instances` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.instances` | Gauge | `{instance}` | Max channel instances | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.instances` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `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) | + + + +## Metric `ibm.mq.connection.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.connection.count` | Gauge | `{connection}` | Active connections count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.connection.count` Attributes + +| 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.manager.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.status` | Gauge | `1` | Queue manager status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.status` Attributes + +| 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.heartbeat` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.heartbeat` | Gauge | `1` | Queue manager heartbeat | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.heartbeat` Attributes + +| 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.archive.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.archive.log.size` | Gauge | `By` | Queue manager archive log size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.archive.log.size` Attributes + +| 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.manager.max.active.channels` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.max.active.channels` | Gauge | `{channel}` | Queue manager max active channels | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.max.active.channels` Attributes + +| 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.manager.statistics.interval` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.statistics.interval` | Gauge | `1` | Queue manager statistics interval | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.statistics.interval` Attributes + +| 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.publish.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.publish.count` | Gauge | `{publication}` | Topic publication count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.publish.count` Attributes + +| 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) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [20] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[20] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.subscription.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.subscription.count` | Gauge | `{subscription}` | Topic subscription count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.subscription.count` Attributes + +| 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) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [21] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[21] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.listener.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.listener.status` | Gauge | `1` | Listener status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.listener.status` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.listener.name` | string | The listener name | `listener` | `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) | + + + +## Metric `ibm.mq.unauthorized.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.unauthorized.event` | Counter | `{event}` | Number of authentication error events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.unauthorized.event` Attributes + +| 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) | +| `service.name` | string | Logical name of the service. [22] | `Wordle`; `JMSService` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `user.name` | string | Short name or login/username of the user. [23] | `foo`; `root` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[22] `service.name`:** This is duplicated from otel semantic-conventions. + +**[23] `user.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.manager.max.handles` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.max.handles` | Gauge | `{event}` | Max open handles | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.max.handles` Attributes + +| 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) | diff --git a/ibm-mq-metrics/model/attributes.yaml b/ibm-mq-metrics/model/attributes.yaml new file mode 100644 index 000000000..ef7ffdafd --- /dev/null +++ b/ibm-mq-metrics/model/attributes.yaml @@ -0,0 +1,75 @@ +groups: + - id: shared.attributes + type: attribute_group + brief: Attributes of metrics. + attributes: + - id: ibm.mq.queue.manager + type: string + brief: > + The name of the IBM queue manager + stability: development + examples: ["MQ1"] + - id: messaging.destination.name + type: string + brief: > + The system-specific name of the messaging operation. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "dev/" ] + - id: ibm.mq.channel.name + type: string + brief: > + The name of the channel + stability: development + examples: [ "DEV.ADMIN.SVRCONN" ] + - id: ibm.mq.channel.type + type: string + brief: > + The type of the channel + stability: development + examples: [ "server-connection", "cluster-receiver", "amqp" ] + - id: ibm.mq.job.name + type: string + brief: > + The job name + stability: development + examples: [ "0000074900000003" ] + - id: ibm.mq.channel.start.time + type: int + brief: > + The start time of the channel as seconds since Epoch. + stability: development + examples: [ 1748462702 ] + # Use the messaging.destination.name attribute instead? +# - id: queue.name +# type: string +# brief: > +# The queue name +# stability: development +# examples: [ "DEV.DEAD.LETTER.QUEUE" ] + - id: ibm.mq.queue.type + type: string + brief: > + The queue type + stability: development + examples: [ "local-normal" ] + - id: ibm.mq.listener.name + type: string + brief: > + The listener name + stability: development + examples: [ "listener" ] + - id: user.name + type: string + brief: > + Short name or login/username of the user. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "foo", "root" ] + - id: service.name + type: string + brief: > + Logical name of the service. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "Wordle", "JMSService" ] diff --git a/ibm-mq-metrics/model/metrics.yaml b/ibm-mq-metrics/model/metrics.yaml new file mode 100644 index 000000000..cdc20456a --- /dev/null +++ b/ibm-mq-metrics/model/metrics.yaml @@ -0,0 +1,611 @@ +groups: + - id: ibm.mq.message.retry.count + type: metric + metric_name: ibm.mq.message.retry.count + stability: development + brief: "Number of message retries" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.status + type: metric + metric_name: ibm.mq.status + stability: development + brief: "Channel status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.max.sharing.conversations + type: metric + metric_name: ibm.mq.max.sharing.conversations + stability: development + brief: "Maximum number of conversations permitted on this channel instance." + instrument: gauge + unit: "{conversation}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.current.sharing.conversations + type: metric + metric_name: ibm.mq.current.sharing.conversations + stability: development + unit: "{conversation}" + brief: "Current number of conversations permitted on this channel instance." + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.byte.received + type: metric + metric_name: ibm.mq.byte.received + stability: development + brief: "Number of bytes received" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.byte.sent + type: metric + metric_name: ibm.mq.byte.sent + stability: development + brief: "Number of bytes sent" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.buffers.received + type: metric + metric_name: ibm.mq.buffers.received + stability: development + brief: "Buffers received" + instrument: gauge + unit: "{buffer}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.buffers.sent + type: metric + metric_name: ibm.mq.buffers.sent + stability: development + brief: "Buffers sent" + unit: "{buffer}" + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.message.count + type: metric + metric_name: ibm.mq.message.count + stability: development + brief: "Message count" + unit: "{message}" + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.open.input.count + type: metric + metric_name: ibm.mq.open.input.count + stability: development + brief: "Count of applications sending messages to the queue" + instrument: gauge + unit: "{application}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.open.output.count + type: metric + metric_name: ibm.mq.open.output.count + stability: development + brief: "Count of applications consuming messages from the queue" + instrument: gauge + unit: "{application}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.high.queue.depth + type: metric + metric_name: ibm.mq.high.queue.depth + stability: development + brief: "The current high queue depth" + instrument: gauge + unit: "{percent}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.service.interval + type: metric + metric_name: ibm.mq.service.interval + stability: development + brief: "The queue service interval" + instrument: gauge + unit: "{percent}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.queue.depth.full.event + type: metric + metric_name: ibm.mq.queue.depth.full.event + stability: development + brief: "The number of full queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.queue.depth.high.event + type: metric + metric_name: ibm.mq.queue.depth.high.event + stability: development + brief: "The number of high queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.queue.depth.low.event + type: metric + metric_name: ibm.mq.queue.depth.low.event + stability: development + brief: "The number of low queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.uncommitted.messages + type: metric + metric_name: ibm.mq.uncommitted.messages + stability: development + brief: "Number of uncommitted messages" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.oldest.msg.age + type: metric + metric_name: ibm.mq.oldest.msg.age + stability: development + brief: "Queue message oldest age" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.current.max.queue.filesize + type: metric + metric_name: ibm.mq.current.max.queue.filesize + stability: development + brief: "Current maximum queue file size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.current.queue.filesize + type: metric + metric_name: ibm.mq.current.queue.filesize + stability: development + brief: "Current queue file size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.instances.per.client + type: metric + metric_name: ibm.mq.instances.per.client + stability: development + brief: "Instances per client" + instrument: gauge + unit: "{instance}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.deq.count + type: metric + metric_name: ibm.mq.message.deq.count + stability: development + brief: "Message dequeue count" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.enq.count + type: metric + metric_name: ibm.mq.message.enq.count + stability: development + brief: "Message enqueue count" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.queue.depth + type: metric + metric_name: ibm.mq.queue.depth + stability: development + brief: "Current queue depth" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.service.interval.event + type: metric + metric_name: ibm.mq.service.interval.event + stability: development + brief: "Queue service interval event" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.reusable.log.size + type: metric + metric_name: ibm.mq.reusable.log.size + stability: development + brief: "The amount of space occupied, in megabytes, by log extents available to be reused." + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.active.channels + type: metric + metric_name: ibm.mq.manager.active.channels + stability: development + brief: "The queue manager active maximum channels limit" + instrument: gauge + unit: "{channel}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.restart.log.size + type: metric + metric_name: ibm.mq.restart.log.size + stability: development + brief: "Size of the log data required for restart recovery in megabytes." + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.max.queue.depth + type: metric + metric_name: ibm.mq.max.queue.depth + stability: development + brief: "Maximum queue depth" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.onqtime.short_period + type: metric + metric_name: ibm.mq.onqtime.short_period + stability: development + brief: "Amount of time, in microseconds, that a message spent on the queue, over a short period" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.onqtime.long_period + type: metric + metric_name: ibm.mq.onqtime.long_period + stability: development + brief: "Amount of time, in microseconds, that a message spent on the queue, over a longer period" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.received.count + type: metric + metric_name: ibm.mq.message.received.count + stability: development + brief: "Number of messages received" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.message.sent.count + type: metric + metric_name: ibm.mq.message.sent.count + stability: development + brief: "Number of messages sent" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.max.instances + type: metric + metric_name: ibm.mq.max.instances + stability: development + brief: "Max channel instances" + instrument: gauge + unit: "{instance}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.connection.count + type: metric + metric_name: ibm.mq.connection.count + stability: development + brief: "Active connections count" + instrument: gauge + unit: "{connection}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.status + type: metric + metric_name: ibm.mq.manager.status + stability: development + brief: "Queue manager status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.heartbeat + type: metric + metric_name: ibm.mq.heartbeat + stability: development + brief: "Queue manager heartbeat" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.archive.log.size + type: metric + metric_name: ibm.mq.archive.log.size + stability: development + brief: "Queue manager archive log size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.max.active.channels + type: metric + metric_name: ibm.mq.manager.max.active.channels + stability: development + brief: "Queue manager max active channels" + instrument: gauge + unit: "{channel}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.statistics.interval + type: metric + metric_name: ibm.mq.manager.statistics.interval + stability: development + brief: "Queue manager statistics interval" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.publish.count + type: metric + metric_name: ibm.mq.publish.count + stability: development + brief: "Topic publication count" + instrument: gauge + unit: "{publication}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.subscription.count + type: metric + metric_name: ibm.mq.subscription.count + stability: development + brief: "Topic subscription count" + instrument: gauge + unit: "{subscription}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.listener.status + type: metric + metric_name: ibm.mq.listener.status + stability: development + brief: "Listener status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.listener.name + requirement_level: required + - id: ibm.mq.unauthorized.event + type: metric + metric_name: ibm.mq.unauthorized.event + stability: development + brief: "Number of authentication error events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: user.name + requirement_level: required + - ref: service.name + requirement_level: required + - id: ibm.mq.manager.max.handles + type: metric + metric_name: ibm.mq.manager.max.handles + stability: development + brief: "Max open handles" + instrument: gauge + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required diff --git a/ibm-mq-metrics/model/registry_manifest.yaml b/ibm-mq-metrics/model/registry_manifest.yaml new file mode 100644 index 000000000..1fb92e0cb --- /dev/null +++ b/ibm-mq-metrics/model/registry_manifest.yaml @@ -0,0 +1,3 @@ +name: IBM MQ Monitoring +semconv_version: v1.34.0 +schema_base_url: https://www.github.com/open-telemetry/opentelemetry-java-contrib/ibm-mq-metrics diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java new file mode 100644 index 000000000..ef40efa44 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java @@ -0,0 +1,293 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import com.ibm.msg.client.jakarta.jms.JmsConnectionFactory; +import com.ibm.msg.client.jakarta.jms.JmsFactoryFactory; +import com.ibm.msg.client.jakarta.wmq.WMQConstants; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.util.WmqUtil; +import jakarta.jms.Destination; +import jakarta.jms.JMSConsumer; +import jakarta.jms.JMSContext; +import jakarta.jms.JMSException; +import jakarta.jms.JMSProducer; +import jakarta.jms.JMSRuntimeException; +import jakarta.jms.TextMessage; + +/** + * This code was adapted from https://github.com/ibm-messaging/mq-dev-samples/. + * + *

A minimal and simple application for Point-to-point messaging. + * + *

Application makes use of fixed literals, any customisations will require re-compilation of + * this source file. Application assumes that the named queue is empty prior to a run. + * + *

Notes: + * + *

API type: Jakarta API (JMS v3.0, simplified domain) + * + *

Messaging domain: Point-to-point + * + *

Provider type: IBM MQ + * + *

Connection mode: Client connection + * + *

JNDI in use: No + */ +public final class JakartaPutGet { + + private JakartaPutGet() {} + + public static void createQueue(QueueManager manager, String name, int maxDepth) { + MQQueueManager ibmQueueManager = WmqUtil.connectToQueueManager(manager); + PCFMessageAgent agent = WmqUtil.initPcfMessageAgent(manager, ibmQueueManager); + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_CREATE_Q); + request.addParameter(com.ibm.mq.constants.CMQC.MQCA_Q_NAME, name); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_LOCAL); + + request.addParameter(CMQC.MQIA_MAX_Q_DEPTH, maxDepth); + // these parameters are indicated in percentage of max depth. + request.addParameter(CMQC.MQIA_Q_DEPTH_HIGH_LIMIT, 75); + request.addParameter(CMQC.MQIA_Q_DEPTH_LOW_LIMIT, 20); + request.addParameter(CMQC.MQIA_Q_DEPTH_HIGH_EVENT, CMQCFC.MQEVR_ENABLED); + request.addParameter(CMQC.MQIA_Q_DEPTH_LOW_EVENT, CMQCFC.MQEVR_ENABLED); + request.addParameter(CMQC.MQIA_Q_DEPTH_MAX_EVENT, CMQCFC.MQEVR_ENABLED); + try { + agent.send(request); + } catch (PCFException e) { + if (e.reasonCode == CMQCFC.MQRCCF_OBJECT_ALREADY_EXISTS) { + return; + } + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + * @param numberOfMessages Number of messages to send + * @param sleepIntervalMs Sleep interval in ms + */ + public static void runPutGet( + QueueManager manager, String queueName, int numberOfMessages, int sleepIntervalMs) { + + createQueue(manager, queueName, 100000); + JMSContext context = null; + JMSContext senderContext = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "JakartaPutGet (Jakarta)"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + JMSConsumer consumer = context.createConsumer(destination); + consumer.setMessageListener(message -> {}); + + senderContext = cf.createContext(); + Destination senderDestination = senderContext.createQueue("queue:///" + queueName); + + for (int i = 0; i < numberOfMessages; i++) { + long uniqueNumber = System.currentTimeMillis() % 1000; + TextMessage message = + senderContext.createTextMessage("Your lucky number today is " + uniqueNumber); + message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, 37); + JMSProducer producer = senderContext.createProducer(); + producer.send(senderDestination, message); + + Thread.sleep(sleepIntervalMs); + } + + } catch (JMSException | InterruptedException jmsex) { + throw new RuntimeException(jmsex); + } finally { + if (context != null) { + context.close(); + } + if (senderContext != null) { + senderContext.close(); + } + } + } + + /** + * Send a number of messages to the queue. + * + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + * @param numberOfMessages Number of messages to send + */ + public static void sendMessages(QueueManager manager, String queueName, int numberOfMessages) { + + createQueue(manager, queueName, 1000); + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Message Sender"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + for (int i = 0; i < numberOfMessages; i++) { + long uniqueNumber = System.currentTimeMillis() % 1000; + TextMessage message = + context.createTextMessage("Your lucky number today is " + uniqueNumber); + message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, 37); + JMSProducer producer = context.createProducer(); + producer.send(destination, message); + } + + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == 2053) { // queue is full + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } + + /** + * Reads all the messages of the queue. + * + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + */ + public static void readMessages(QueueManager manager, String queueName) { + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Message Receiver"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + JMSConsumer consumer = context.createConsumer(destination); // autoclosable + while (consumer.receiveBody(String.class, 100) != null) {} + + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == CMQC.MQRC_NO_MSG_AVAILABLE) { // out of messages, we read them all. + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } + + public static void tryLoginWithBadPassword(QueueManager manager) { + + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Bad Password"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, "badpassword"); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == 2035) { // bad password + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } +} diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java new file mode 100644 index 000000000..8221c9353 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.WmqMonitor; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +/** + * The TestWMQMonitor class extends the WMQMonitor class and provides a test implementation of the + * WebSphere MQ monitoring functionality. It is intended for internal integration test purposes and + * facilitates custom configuration through a test configuration file and a test metric write + * helper. + */ +class TestWMQMonitor { + + private final ConfigWrapper config; + private final ExecutorService threadPool; + private final Meter meter; + + TestWMQMonitor(ConfigWrapper config, Meter meter, ExecutorService service) { + this.config = config; + this.threadPool = service; + this.meter = meter; + } + + /** + * Executes a test run for monitoring WebSphere MQ queue managers based on the provided + * configuration "testConfigFile". + * + *

The method retrieves "queueManagers" from the yml configuration file and uses a custom + * MetricWriteHelper if provided, initializes a TasksExecutionServiceProvider, and executes the + * WMQMonitorTask + */ + void runTest() { + List> queueManagers = config.getQueueManagers(); + assertThat(queueManagers).isNotNull(); + ObjectMapper mapper = new ObjectMapper(); + + WmqMonitor wmqTask = new WmqMonitor(config, threadPool, meter); + + // we override this helper to pass in our opentelemetry helper instead. + for (Map queueManager : queueManagers) { + QueueManager qManager = mapper.convertValue(queueManager, QueueManager.class); + wmqTask.run(qManager); + } + } +} 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 new file mode 100644 index 000000000..9f0a64f21 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java @@ -0,0 +1,284 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +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.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.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Integration Test for WMQMonitor */ +@Disabled +class WMQMonitorIntegrationTest { + + private static final Logger logger = LoggerFactory.getLogger(WMQMonitorIntegrationTest.class); + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + private static final ExecutorService service = + Executors.newFixedThreadPool( + 4, /* one gets burned with our @BeforeAll message uzi, 4 is faster than 2 */ + r -> { + Thread thread = new Thread(r); + thread.setUncaughtExceptionHandler( + (t, e) -> { + logger.error("Uncaught exception", e); + fail(e.getMessage()); + }); + thread.setDaemon(true); + thread.setName("WMQMonitorIntegrationTest"); + return thread; + }); + + private static QueueManager getQueueManagerConfig() throws Exception { + String configFile = getConfigFile("conf/test-config.yml"); + ConfigWrapper wrapper = ConfigWrapper.parse(configFile); + Map queueManagerConfig = wrapper.getQueueManagers().get(0); + ObjectMapper mapper = new ObjectMapper(); + return mapper.convertValue(queueManagerConfig, QueueManager.class); + } + + @NotNull + private static String getConfigFile(String resourcePath) throws URISyntaxException { + URL resource = WMQMonitorIntegrationTest.class.getClassLoader().getResource(resourcePath); + if (resource == null) { + throw new IllegalArgumentException("file not found!"); + } + + File file = Paths.get(resource.toURI()).toFile(); + logger.info("Config file: {}", file.getAbsolutePath()); + return file.getAbsolutePath(); + } + + private static void configureQueueManager(QueueManager manager) { + MQQueueManager ibmQueueManager = WmqUtil.connectToQueueManager(manager); + PCFMessageAgent agent = WmqUtil.initPcfMessageAgent(manager, ibmQueueManager); + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_CHANGE_Q_MGR); + // turn on emitting authority events + request.addParameter(CMQC.MQIA_AUTHORITY_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting configuration events + request.addParameter(CMQC.MQIA_CONFIGURATION_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting channel auto-definition events + request.addParameter(CMQC.MQIA_CHANNEL_AUTO_DEF_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting channel events + request.addParameter(CMQC.MQIA_CHANNEL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting command events + request.addParameter(CMQC.MQIA_COMMAND_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting inhibit events + request.addParameter(CMQC.MQIA_INHIBIT_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting local events + request.addParameter(CMQC.MQIA_LOCAL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting performance events + request.addParameter(CMQC.MQIA_PERFORMANCE_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting remote events + request.addParameter(CMQC.MQIA_REMOTE_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting SSL events + request.addParameter(CMQC.MQIA_SSL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting start/stop events + request.addParameter(CMQC.MQIA_START_STOP_EVENT, CMQCFC.MQEVR_ENABLED); + try { + agent.send(request); + } catch (Exception e) { + if (e instanceof PCFException) { + PCFMessage[] msgs = (PCFMessage[]) ((PCFException) e).exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + } + throw new RuntimeException(e); + } + } + + @BeforeAll + public static void sendClientMessages() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + configureQueueManager(qManager); + + // create a queue and fill it up past its capacity. + JakartaPutGet.createQueue(qManager, "smallqueue", 10); + + JakartaPutGet.runPutGet(qManager, "myqueue", 10, 1); + + Future ignored = + service.submit(() -> JakartaPutGet.runPutGet(qManager, "myqueue", 1000000, 100)); + } + + @AfterAll + public static void stopSendingClientMessages() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + configureQueueManager(qManager); + + service.shutdown(); + } + + @BeforeEach + void setUpEvents() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + // try to login with a bad password: + JakartaPutGet.tryLoginWithBadPassword(qManager); + + JakartaPutGet.sendMessages(qManager, "smallqueue", 1); + Thread.sleep(1000); + JakartaPutGet.sendMessages(qManager, "smallqueue", 8); + Thread.sleep(1000); + JakartaPutGet.sendMessages(qManager, "smallqueue", 5); + } + + @AfterEach + void clearQueue() throws Exception { + // clear the full queue. + JakartaPutGet.readMessages(getQueueManagerConfig(), "smallqueue"); + } + + @Test + void test_monitor_with_full_config() throws Exception { + String configFile = getConfigFile("conf/test-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(); + Map metrics = new HashMap<>(); + for (MetricData metricData : data) { + metrics.put(metricData.getName(), metricData); + } + Set metricNames = metrics.keySet(); + // this value is read from the active channels count: + assertThat(metricNames).contains("ibm.mq.manager.active.channels"); + // this value is read from the configuration queue. + assertThat(metricNames).contains("ibm.mq.manager.max.handles"); + // this value is read from the queue manager events, for unauthorized events. + assertThat(metricNames).contains("ibm.mq.unauthorized.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.full.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.high.event"); + assertThat(metricNames).contains("ibm.mq.queue.depth.low.event"); + // reads a value from the heartbeat gauge + assertThat(metricNames).contains("ibm.mq.heartbeat"); + assertThat(metricNames).contains("ibm.mq.oldest.msg.age"); + if (metrics.get("ibm.mq.oldest.msg.age") != null) { + Set queueNames = + metrics.get("ibm.mq.oldest.msg.age").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(MESSAGING_DESTINATION_NAME)) + .collect(Collectors.toSet()); + assertThat(queueNames).contains("smallqueue"); + } + // make sure we get MQ manager status + assertThat(metricNames).contains("ibm.mq.manager.status"); + if (metrics.get("ibm.mq.manager.status") != null) { + Set queueManagers = + metrics.get("ibm.mq.manager.status").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(IBM_MQ_QUEUE_MANAGER)) + .collect(Collectors.toSet()); + assertThat(queueManagers).contains("QM1"); + } + + assertThat(metricNames).contains("ibm.mq.onqtime.2"); + if (metrics.get("ibm.mq.onqtime.2") != null) { + Set queueNames = + metrics.get("ibm.mq.onqtime.2").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(MESSAGING_DESTINATION_NAME)) + .collect(Collectors.toSet()); + assertThat(queueNames).contains("smallqueue"); + Set queueManagers = + metrics.get("ibm.mq.manager.status").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(IBM_MQ_QUEUE_MANAGER)) + .collect(Collectors.toSet()); + assertThat(queueManagers).contains("QM1"); + // TODO: Add more asserts about data values, units, attributes, etc, not just names + } + } + + @Test + void test_wmqmonitor() throws Exception { + String configFile = getConfigFile("conf/test-queuemgr-config.yml"); + ConfigWrapper config = ConfigWrapper.parse(configFile); + Meter meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + + TestWMQMonitor monitor = new TestWMQMonitor(config, meter, service); + monitor.runTest(); + // TODO: Wait why are there no asserts here? + } + + @Test + void test_otlphttp() throws Exception { + ConfigWrapper config = + ConfigWrapper.parse(WMQMonitorIntegrationTest.getConfigFile("conf/test-config.yml")); + ScheduledExecutorService service = + Executors.newScheduledThreadPool(config.getNumberOfThreads()); + Main.run(config, service, otelTesting.getOpenTelemetry()); + CountDownLatch latch = new CountDownLatch(1); + Future ignored = service.submit(latch::countDown); + Thread.sleep(5000); // TODO: This is fragile and time consuming and should be made better + service.shutdown(); + assertTrue(service.awaitTermination(30, TimeUnit.SECONDS)); + + List data = otelTesting.getMetrics(); + Set metricNames = new HashSet<>(); + for (MetricData metricData : data) { + metricNames.add(metricData.getName()); + } + // this value is read from the active channels count: + assertThat(metricNames).contains("ibm.mq.manager.active.channels"); + // this value is read from the configuration queue. + assertThat(metricNames).contains("ibm.mq.manager.max.handles"); + // this value is read from the queue manager events, for unauthorized events. + assertThat(metricNames).contains("ibm.mq.unauthorized.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.full.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.high.event"); + assertThat(metricNames).contains("ibm.mq.queue.depth.low.event"); + // reads a value from the heartbeat gauge + assertThat(metricNames).contains("ibm.mq.heartbeat"); + } +} diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml new file mode 100644 index 000000000..0093bc9e7 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml @@ -0,0 +1,197 @@ + +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #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" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "TLS_AES_128_GCM_SHA256" + + + 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"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + +# 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 + otel.exporter.otlp.protocol: http/protobuf + otel.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml new file mode 100644 index 000000000..9d2808906 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml @@ -0,0 +1,170 @@ +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #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"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + + +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.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java new file mode 100644 index 000000000..6ded88547 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq; + +import com.ibm.mq.constants.CMQC; +import io.opentelemetry.ibm.mq.config.QueueManager; +import java.util.Hashtable; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Takes care of websphere mq connection, authentication, SSL, Cipher spec, certificate based + * authorization.
+ * It also validates the arguments passed for various scenarios. + */ +public class WmqContext { + private static final String TRANSPORT_TYPE_CLIENT = "Client"; + private static final String TRANSPORT_TYPE_BINDINGS = "Bindings"; + + public static final Logger logger = LoggerFactory.getLogger(WmqContext.class); + private final QueueManager queueManager; + + public WmqContext(QueueManager queueManager) { + this.queueManager = queueManager; + validateArgs(); + } + + /** Note: This Hashtable type is needed for IBM client classes. */ + @SuppressWarnings("JdkObsolete") + public Hashtable getMqEnvironment() { + Hashtable env = new Hashtable<>(); + addEnvProperty(env, CMQC.HOST_NAME_PROPERTY, queueManager.getHost()); + addEnvProperty(env, CMQC.PORT_PROPERTY, queueManager.getPort()); + addEnvProperty(env, CMQC.CHANNEL_PROPERTY, queueManager.getChannelName()); + addEnvProperty(env, CMQC.USER_ID_PROPERTY, queueManager.getUsername()); + addEnvProperty(env, CMQC.PASSWORD_PROPERTY, queueManager.getPassword()); + addEnvProperty(env, CMQC.SSL_CERT_STORE_PROPERTY, queueManager.getSslKeyRepository()); + addEnvProperty(env, CMQC.SSL_CIPHER_SUITE_PROPERTY, queueManager.getCipherSuite()); + // TODO: investigate on CIPHER_SPEC property No Available in MQ 7.5 Jar + + if (TRANSPORT_TYPE_CLIENT.equalsIgnoreCase(queueManager.getTransportType())) { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_CLIENT); + } else if (TRANSPORT_TYPE_BINDINGS.equalsIgnoreCase(queueManager.getTransportType())) { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_BINDINGS); + } else { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES); + } + + if (logger.isDebugEnabled()) { + logger.debug("Transport property is {}", env.get(CMQC.TRANSPORT_PROPERTY)); + } + return env; + } + + @SuppressWarnings({"unused", "unchecked", "rawtypes"}) + private static void addEnvProperty(Hashtable env, String propName, @Nullable Object propVal) { + if (null != propVal) { + if (propVal instanceof String) { + String propString = (String) propVal; + if (propString.isEmpty()) { + return; + } + } + env.put(propName, propVal); + } + } + + private void validateArgs() { + boolean validArgs = true; + StringBuilder errorMsg = new StringBuilder(); + if (queueManager == null) { + validArgs = false; + errorMsg.append("Queue manager cannot be null"); + } else { + if (TRANSPORT_TYPE_CLIENT.equalsIgnoreCase(queueManager.getTransportType())) { + if (queueManager.getHost() == null || queueManager.getHost().trim().isEmpty()) { + validArgs = false; + errorMsg.append("Host cannot be null or empty for client type connection. "); + } + if (queueManager.getPort() == -1) { + validArgs = false; + errorMsg.append("port should be set for client type connection. "); + } + if (queueManager.getChannelName() == null + || queueManager.getChannelName().trim().isEmpty()) { + validArgs = false; + errorMsg.append("Channel cannot be null or empty for client type connection. "); + } + } + if (TRANSPORT_TYPE_BINDINGS.equalsIgnoreCase(queueManager.getTransportType())) { + if (queueManager.getName() == null || queueManager.getName().trim().isEmpty()) { + validArgs = false; + errorMsg.append("queuemanager cannot be null or empty for bindings type connection. "); + } + } + } + + if (!validArgs) { + throw new IllegalArgumentException(errorMsg.toString()); + } + } +} 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 new file mode 100644 index 000000000..7eaa2d8c0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -0,0 +1,209 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.common.Attributes; +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.MetricsConfig; +import io.opentelemetry.ibm.mq.metricscollector.ChannelMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.InquireChannelCmdCollector; +import io.opentelemetry.ibm.mq.metricscollector.InquireQueueManagerCmdCollector; +import io.opentelemetry.ibm.mq.metricscollector.ListenerMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.MetricsCollectorContext; +import io.opentelemetry.ibm.mq.metricscollector.PerformanceEventQueueCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueManagerEventCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueManagerMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.ReadConfigurationEventQueueCollector; +import io.opentelemetry.ibm.mq.metricscollector.TopicMetricsCollector; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.ibm.mq.util.WmqUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WmqMonitor { + + private static final Logger logger = LoggerFactory.getLogger(WmqMonitor.class); + + private final List queueManagers; + private final List> jobs = new ArrayList<>(); + private final LongGauge heartbeatGauge; + private final ExecutorService threadPool; + private final MetricsConfig metricsConfig; + + public WmqMonitor(ConfigWrapper config, ExecutorService threadPool, Meter meter) { + List> queueManagers = getQueueManagers(config); + ObjectMapper mapper = new ObjectMapper(); + + this.queueManagers = new ArrayList<>(); + + for (Map queueManager : queueManagers) { + try { + QueueManager qManager = mapper.convertValue(queueManager, QueueManager.class); + this.queueManagers.add(qManager); + } catch (Throwable t) { + logger.error("Error preparing queue manager {}", queueManager, t); + } + } + + this.metricsConfig = new MetricsConfig(config); + + this.heartbeatGauge = meter.gaugeBuilder("ibm.mq.heartbeat").setUnit("1").ofLongs().build(); + this.threadPool = threadPool; + + jobs.add(new QueueManagerMetricsCollector(meter)); + jobs.add(new InquireQueueManagerCmdCollector(meter)); + jobs.add(new ChannelMetricsCollector(meter)); + jobs.add(new InquireChannelCmdCollector(meter)); + jobs.add(new QueueMetricsCollector(meter, threadPool, config)); + jobs.add(new ListenerMetricsCollector(meter)); + jobs.add(new TopicMetricsCollector(meter)); + jobs.add(new ReadConfigurationEventQueueCollector(meter)); + jobs.add(new PerformanceEventQueueCollector(meter)); + jobs.add(new QueueManagerEventCollector(meter)); + } + + public void run() { + for (QueueManager qm : this.queueManagers) { + run(qm); + } + } + + public void run(QueueManager queueManager) { + String queueManagerName = queueManager.getName(); + logger.debug("WMQMonitor thread for queueManager {} started.", queueManagerName); + long startTime = System.currentTimeMillis(); + MQQueueManager ibmQueueManager = null; + PCFMessageAgent agent = null; + int heartBeatMetricValue = 0; + try { + ibmQueueManager = WmqUtil.connectToQueueManager(queueManager); + heartBeatMetricValue = 1; + agent = WmqUtil.initPcfMessageAgent(queueManager, ibmQueueManager); + extractAndReportMetrics(ibmQueueManager, queueManager, agent); + } catch (RuntimeException e) { + logger.error( + "Error connecting to QueueManager {} by thread {}: {}", + queueManagerName, + Thread.currentThread().getName(), + e.getMessage(), + e); + } finally { + if (this.metricsConfig.isIbmMqHeartbeatEnabled()) { + heartbeatGauge.set( + heartBeatMetricValue, Attributes.of(IBM_MQ_QUEUE_MANAGER, queueManagerName)); + } + cleanUp(ibmQueueManager, agent); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "WMQMonitor thread for queueManager {} ended. Time taken = {} ms", + queueManagerName, + endTime); + } + } + + @NotNull + private static List> getQueueManagers(ConfigWrapper config) { + List> queueManagers = config.getQueueManagers(); + if (queueManagers.isEmpty()) { + throw new IllegalStateException( + "The 'queueManagers' section in config.yml is empty or otherwise incorrect."); + } + return queueManagers; + } + + private void extractAndReportMetrics( + MQQueueManager mqQueueManager, QueueManager queueManager, PCFMessageAgent agent) { + logger.debug("Queueing {} jobs", jobs.size()); + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, agent, mqQueueManager, this.metricsConfig); + List> tasks = new ArrayList<>(); + for (Consumer collector : jobs) { + tasks.add( + () -> { + try { + long startTime = System.currentTimeMillis(); + collector.accept(context); + long diffTime = System.currentTimeMillis() - startTime; + if (diffTime > 60000L) { + logger.warn( + "{} Task took {} ms to complete", + collector.getClass().getSimpleName(), + diffTime); + } else { + logger.debug( + "{} Task took {} ms to complete", + collector.getClass().getSimpleName(), + diffTime); + } + } catch (RuntimeException e) { + logger.error( + "Error while running task name = {}", collector.getClass().getSimpleName(), e); + } + return null; + }); + } + + try { + this.threadPool.invokeAll(tasks); + } catch (InterruptedException e) { + logger.error("Error while the thread {} is waiting ", Thread.currentThread().getName(), e); + } + } + + /** Destroy the agent and disconnect from queue manager */ + private static void cleanUp( + @Nullable MQQueueManager ibmQueueManager, @Nullable PCFMessageAgent agent) { + // Disconnect the agent. + + if (agent != null) { + String qMgrName = agent.getQManagerName(); + try { + agent.disconnect(); + logger.debug( + "PCFMessageAgent disconnected for queueManager {} in thread {}", + qMgrName, + Thread.currentThread().getName()); + } catch (Exception e) { + logger.error( + "Error occurred while disconnecting PCFMessageAgent for queueManager {} in thread {}", + qMgrName, + Thread.currentThread().getName(), + e); + } + } + + // Disconnect queue manager + if (ibmQueueManager != null) { + String name = ""; + try { + name = ibmQueueManager.getName(); + ibmQueueManager.disconnect(); + } catch (Exception e) { + logger.error( + "Error occurred while disconnecting queueManager {} in thread {}", + name, + Thread.currentThread().getName(), + e); + } + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java new file mode 100644 index 000000000..f02f05cc0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import io.opentelemetry.ibm.mq.metricscollector.FilterType; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** A jackson databind class used for config. */ +public class ExcludeFilters { + + private String type = "UNKNOWN"; + private Set values = new HashSet<>(); + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Set getValues() { + return Collections.unmodifiableSet(values); + } + + public void setValues(Set values) { + this.values = new HashSet<>(values); + } + + public static boolean isExcluded(String resourceName, Collection excludeFilters) { + if (excludeFilters == null) { + return false; + } + for (ExcludeFilters filter : excludeFilters) { + if (filter.isExcluded(resourceName)) { + return true; + } + } + return false; + } + + public boolean isExcluded(String resourceName) { + if (resourceName == null || resourceName.isEmpty()) { + return true; + } + switch (FilterType.valueOf(type)) { + case CONTAINS: + for (String filterValue : values) { + if (resourceName.contains(filterValue)) { + return true; + } + } + break; + case STARTSWITH: + for (String filterValue : values) { + if (resourceName.startsWith(filterValue)) { + return true; + } + } + break; + case NONE: + return false; + case EQUALS: + for (String filterValue : values) { + if (resourceName.equals(filterValue)) { + return true; + } + } + break; + case ENDSWITH: + for (String filterValue : values) { + if (resourceName.endsWith(filterValue)) { + return true; + } + } + } + return false; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java new file mode 100644 index 000000000..685840977 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java @@ -0,0 +1,257 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import javax.annotation.Nullable; + +/** This is a jackson databind class used purely for config. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueueManager { + + @Nullable private String host; + private int port = -1; + private String name = "UNKNOWN"; + @Nullable private String channelName; + @Nullable private String transportType; + @Nullable private String username; + @Nullable private String password; + @Nullable private String sslKeyRepository; + private int ccsid = Integer.MIN_VALUE; + private int encoding = Integer.MIN_VALUE; + @Nullable private String cipherSuite; + @Nullable private String cipherSpec; + @Nullable private String replyQueuePrefix; + @Nullable private String modelQueueName; + private String configurationQueueName = "SYSTEM.ADMIN.CONFIG.EVENT"; + private String performanceEventsQueueName = "SYSTEM.ADMIN.PERFM.EVENT"; + private String queueManagerEventsQueueName = "SYSTEM.ADMIN.QMGR.EVENT"; + private long consumeConfigurationEventInterval; + private boolean refreshQueueManagerConfigurationEnabled; + // Config default is 100. + // https://www.ibm.com/docs/en/ibm-mq/9.3.x?topic=qmini-channels-stanza-file + private int maxActiveChannels = 100; + + @Nullable private ResourceFilters queueFilters; + @Nullable private ResourceFilters channelFilters; + @Nullable private ResourceFilters listenerFilters; + @Nullable private ResourceFilters topicFilters; + + @Nullable + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Nullable + public String getChannelName() { + return channelName; + } + + public void setChannelName(@Nullable String channelName) { + this.channelName = channelName; + } + + @Nullable + public String getTransportType() { + return transportType; + } + + public void setTransportType(@Nullable String transportType) { + this.transportType = transportType; + } + + @Nullable + public String getUsername() { + return username; + } + + public void setUsername(@Nullable String username) { + this.username = username; + } + + @Nullable + public String getPassword() { + return password; + } + + public void setPassword(@Nullable String password) { + this.password = password; + } + + public ResourceFilters getQueueFilters() { + if (queueFilters == null) { + return new ResourceFilters(); + } + return queueFilters; + } + + public void setQueueFilters(@Nullable ResourceFilters queueFilters) { + this.queueFilters = queueFilters; + } + + @Nullable + public String getSslKeyRepository() { + return sslKeyRepository; + } + + public void setSslKeyRepository(@Nullable String sslKeyRepository) { + this.sslKeyRepository = sslKeyRepository; + } + + @Nullable + public String getCipherSuite() { + return cipherSuite; + } + + public void setCipherSuite(String cipherSuite) { + this.cipherSuite = cipherSuite; + } + + @Nullable + public String getCipherSpec() { + return cipherSpec; + } + + public void setCipherSpec(@Nullable String cipherSpec) { + this.cipherSpec = cipherSpec; + } + + public ResourceFilters getChannelFilters() { + if (channelFilters == null) { + return new ResourceFilters(); + } + return channelFilters; + } + + public void setChannelFilters(@Nullable ResourceFilters channelFilters) { + this.channelFilters = channelFilters; + } + + @Nullable + public String getReplyQueuePrefix() { + return replyQueuePrefix; + } + + public void setReplyQueuePrefix(@Nullable String replyQueuePrefix) { + this.replyQueuePrefix = replyQueuePrefix; + } + + @Nullable + public String getModelQueueName() { + return modelQueueName; + } + + public void setModelQueueName(@Nullable String modelQueueName) { + this.modelQueueName = modelQueueName; + } + + public ResourceFilters getListenerFilters() { + if (listenerFilters == null) { + return new ResourceFilters(); + } + return listenerFilters; + } + + public void setListenerFilters(@Nullable ResourceFilters listenerFilters) { + this.listenerFilters = listenerFilters; + } + + public int getCcsid() { + return ccsid; + } + + public void setCcsid(int ccsid) { + this.ccsid = ccsid; + } + + public int getEncoding() { + return encoding; + } + + public void setEncoding(int encoding) { + this.encoding = encoding; + } + + public ResourceFilters getTopicFilters() { + if (topicFilters == null) { + return new ResourceFilters(); + } + return topicFilters; + } + + public void setTopicFilters(@Nullable ResourceFilters topicFilters) { + this.topicFilters = topicFilters; + } + + public String getConfigurationQueueName() { + return this.configurationQueueName; + } + + public void setConfigurationQueueName(String configurationQueueName) { + this.configurationQueueName = configurationQueueName; + } + + public long getConsumeConfigurationEventInterval() { + return this.consumeConfigurationEventInterval; + } + + public void setConsumeConfigurationEventInterval(long consumeConfigurationEventInterval) { + this.consumeConfigurationEventInterval = consumeConfigurationEventInterval; + } + + public boolean isRefreshQueueManagerConfigurationEnabled() { + return refreshQueueManagerConfigurationEnabled; + } + + public void setRefreshQueueManagerConfigurationEnabled( + boolean refreshQueueManagerConfigurationEnabled) { + this.refreshQueueManagerConfigurationEnabled = refreshQueueManagerConfigurationEnabled; + } + + public String getPerformanceEventsQueueName() { + return performanceEventsQueueName; + } + + public void setPerformanceEventsQueueName(String performanceEventsQueueName) { + this.performanceEventsQueueName = performanceEventsQueueName; + } + + public String getQueueManagerEventsQueueName() { + return this.queueManagerEventsQueueName; + } + + public void setQueueManagerEventsQueueName(String queueManagerEventsQueueName) { + this.queueManagerEventsQueueName = queueManagerEventsQueueName; + } + + public int getMaxActiveChannels() { + return maxActiveChannels; + } + + public void setMaxActiveChannels(int maxActiveChannels) { + this.maxActiveChannels = maxActiveChannels; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java new file mode 100644 index 000000000..72a3a9f42 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import java.util.HashSet; +import java.util.Set; + +public class ResourceFilters { + + private Set include = new HashSet<>(); + private Set exclude = new HashSet<>(); + + public Set getInclude() { + return include; + } + + public void setInclude(Set include) { + this.include = include; + } + + public Set getExclude() { + return exclude; + } + + public void setExclude(Set exclude) { + this.exclude = exclude; + } +} 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 new file mode 100644 index 000000000..6caf87c48 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; + +// This file is generated using weaver. Do not edit manually. + +/** Attribute definitions generated from a Weaver model. Do not edit manually. */ +public final class IbmMqAttributes { + + /** The name of the IBM queue manager */ + public static final AttributeKey IBM_MQ_QUEUE_MANAGER = stringKey("ibm.mq.queue.manager"); + + /** The system-specific name of the messaging operation. */ + public static final AttributeKey MESSAGING_DESTINATION_NAME = + stringKey("messaging.destination.name"); + + /** The name of the channel */ + public static final AttributeKey IBM_MQ_CHANNEL_NAME = stringKey("ibm.mq.channel.name"); + + /** The type of the channel */ + public static final AttributeKey IBM_MQ_CHANNEL_TYPE = stringKey("ibm.mq.channel.type"); + + /** The job name */ + public static final AttributeKey IBM_MQ_JOB_NAME = stringKey("ibm.mq.job.name"); + + /** The start time of the channel as seconds since Epoch. */ + public static final AttributeKey IBM_MQ_CHANNEL_START_TIME = + longKey("ibm.mq.channel.start.time"); + + /** The queue type */ + public static final AttributeKey IBM_MQ_QUEUE_TYPE = stringKey("ibm.mq.queue.type"); + + /** The listener name */ + public static final AttributeKey IBM_MQ_LISTENER_NAME = stringKey("ibm.mq.listener.name"); + + /** Short name or login/username of the user. */ + public static final AttributeKey USER_NAME = stringKey("user.name"); + + /** Logical name of the service. */ + public static final AttributeKey SERVICE_NAME = stringKey("service.name"); + + 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 new file mode 100644 index 000000000..c54c9a431 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java @@ -0,0 +1,424 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Function; + +// This file is generated using weaver. Do not edit manually. + +/** Metric definitions generated from a Weaver model. Do not edit manually. */ +public final class Metrics { + public static final Function MIBY_TO_BYTES = x -> x * 1024L * 1024L; + + private Metrics() {} + + public static LongGauge createIbmMqMessageRetryCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.retry.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of message retries") + .build(); + } + + public static LongGauge createIbmMqStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.status") + .ofLongs() + .setUnit("1") + .setDescription("Channel status") + .build(); + } + + public static LongGauge createIbmMqMaxSharingConversations(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.sharing.conversations") + .ofLongs() + .setUnit("{conversation}") + .setDescription("Maximum number of conversations permitted on this channel instance.") + .build(); + } + + public static LongGauge createIbmMqCurrentSharingConversations(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.sharing.conversations") + .ofLongs() + .setUnit("{conversation}") + .setDescription("Current number of conversations permitted on this channel instance.") + .build(); + } + + public static LongGauge createIbmMqByteReceived(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.byte.received") + .ofLongs() + .setUnit("By") + .setDescription("Number of bytes received") + .build(); + } + + public static LongGauge createIbmMqByteSent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.byte.sent") + .ofLongs() + .setUnit("By") + .setDescription("Number of bytes sent") + .build(); + } + + public static LongGauge createIbmMqBuffersReceived(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.buffers.received") + .ofLongs() + .setUnit("{buffer}") + .setDescription("Buffers received") + .build(); + } + + public static LongGauge createIbmMqBuffersSent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.buffers.sent") + .ofLongs() + .setUnit("{buffer}") + .setDescription("Buffers sent") + .build(); + } + + public static LongGauge createIbmMqMessageCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message count") + .build(); + } + + public static LongGauge createIbmMqOpenInputCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.open.input.count") + .ofLongs() + .setUnit("{application}") + .setDescription("Count of applications sending messages to the queue") + .build(); + } + + public static LongGauge createIbmMqOpenOutputCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.open.output.count") + .ofLongs() + .setUnit("{application}") + .setDescription("Count of applications consuming messages from the queue") + .build(); + } + + public static LongGauge createIbmMqHighQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.high.queue.depth") + .ofLongs() + .setUnit("{percent}") + .setDescription("The current high queue depth") + .build(); + } + + public static LongGauge createIbmMqServiceInterval(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.service.interval") + .ofLongs() + .setUnit("{percent}") + .setDescription("The queue service interval") + .build(); + } + + public static LongCounter createIbmMqQueueDepthFullEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.full.event") + .setUnit("{event}") + .setDescription("The number of full queue events") + .build(); + } + + public static LongCounter createIbmMqQueueDepthHighEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.high.event") + .setUnit("{event}") + .setDescription("The number of high queue events") + .build(); + } + + public static LongCounter createIbmMqQueueDepthLowEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.low.event") + .setUnit("{event}") + .setDescription("The number of low queue events") + .build(); + } + + public static LongGauge createIbmMqUncommittedMessages(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.uncommitted.messages") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of uncommitted messages") + .build(); + } + + public static LongGauge createIbmMqOldestMsgAge(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.oldest.msg.age") + .ofLongs() + .setUnit("microseconds") + .setDescription("Queue message oldest age") + .build(); + } + + public static LongGauge createIbmMqCurrentMaxQueueFilesize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.max.queue.filesize") + .ofLongs() + .setUnit("By") + .setDescription("Current maximum queue file size") + .build(); + } + + public static LongGauge createIbmMqCurrentQueueFilesize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.queue.filesize") + .ofLongs() + .setUnit("By") + .setDescription("Current queue file size") + .build(); + } + + public static LongGauge createIbmMqInstancesPerClient(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.instances.per.client") + .ofLongs() + .setUnit("{instance}") + .setDescription("Instances per client") + .build(); + } + + public static LongGauge createIbmMqMessageDeqCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.deq.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message dequeue count") + .build(); + } + + public static LongGauge createIbmMqMessageEnqCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.enq.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message enqueue count") + .build(); + } + + public static LongGauge createIbmMqQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.queue.depth") + .ofLongs() + .setUnit("{message}") + .setDescription("Current queue depth") + .build(); + } + + public static LongGauge createIbmMqServiceIntervalEvent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.service.interval.event") + .ofLongs() + .setUnit("1") + .setDescription("Queue service interval event") + .build(); + } + + public static LongGauge createIbmMqReusableLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.reusable.log.size") + .ofLongs() + .setUnit("By") + .setDescription( + "The amount of space occupied, in megabytes, by log extents available to be reused.") + .build(); + } + + public static LongGauge createIbmMqManagerActiveChannels(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.active.channels") + .ofLongs() + .setUnit("{channel}") + .setDescription("The queue manager active maximum channels limit") + .build(); + } + + public static LongGauge createIbmMqRestartLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.restart.log.size") + .ofLongs() + .setUnit("By") + .setDescription("Size of the log data required for restart recovery in megabytes.") + .build(); + } + + public static LongGauge createIbmMqMaxQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.queue.depth") + .ofLongs() + .setUnit("{message}") + .setDescription("Maximum queue depth") + .build(); + } + + public static LongGauge createIbmMqOnqtimeShortPeriod(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.onqtime.short_period") + .ofLongs() + .setUnit("microseconds") + .setDescription( + "Amount of time, in microseconds, that a message spent on the queue, over a short period") + .build(); + } + + public static LongGauge createIbmMqOnqtimeLongPeriod(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.onqtime.long_period") + .ofLongs() + .setUnit("microseconds") + .setDescription( + "Amount of time, in microseconds, that a message spent on the queue, over a longer period") + .build(); + } + + public static LongGauge createIbmMqMessageReceivedCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.received.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of messages received") + .build(); + } + + public static LongGauge createIbmMqMessageSentCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.sent.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of messages sent") + .build(); + } + + public static LongGauge createIbmMqMaxInstances(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.instances") + .ofLongs() + .setUnit("{instance}") + .setDescription("Max channel instances") + .build(); + } + + public static LongGauge createIbmMqConnectionCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.connection.count") + .ofLongs() + .setUnit("{connection}") + .setDescription("Active connections count") + .build(); + } + + public static LongGauge createIbmMqManagerStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.status") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager status") + .build(); + } + + public static LongGauge createIbmMqHeartbeat(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.heartbeat") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager heartbeat") + .build(); + } + + public static LongGauge createIbmMqArchiveLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.archive.log.size") + .ofLongs() + .setUnit("By") + .setDescription("Queue manager archive log size") + .build(); + } + + public static LongGauge createIbmMqManagerMaxActiveChannels(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.max.active.channels") + .ofLongs() + .setUnit("{channel}") + .setDescription("Queue manager max active channels") + .build(); + } + + public static LongGauge createIbmMqManagerStatisticsInterval(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.statistics.interval") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager statistics interval") + .build(); + } + + public static LongGauge createIbmMqPublishCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.publish.count") + .ofLongs() + .setUnit("{publication}") + .setDescription("Topic publication count") + .build(); + } + + public static LongGauge createIbmMqSubscriptionCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.subscription.count") + .ofLongs() + .setUnit("{subscription}") + .setDescription("Topic subscription count") + .build(); + } + + public static LongGauge createIbmMqListenerStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.listener.status") + .ofLongs() + .setUnit("1") + .setDescription("Listener status") + .build(); + } + + public static LongCounter createIbmMqUnauthorizedEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.unauthorized.event") + .setUnit("{event}") + .setDescription("Number of authentication error events") + .build(); + } + + public static LongGauge createIbmMqManagerMaxHandles(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.max.handles") + .ofLongs() + .setUnit("{event}") + .setDescription("Max open handles") + .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 new file mode 100644 index 000000000..c172cd532 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java @@ -0,0 +1,213 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.Map; + +// This file is generated using weaver. Do not edit manually. + +/** Configuration of metrics as defined in config.yml. */ +public final class MetricsConfig { + + private final Map config; + + public MetricsConfig(ConfigWrapper config) { + this.config = config.getMetrics(); + } + + public boolean isIbmMqMessageRetryCountEnabled() { + return isEnabled("ibm.mq.message.retry.count"); + } + + public boolean isIbmMqStatusEnabled() { + return isEnabled("ibm.mq.status"); + } + + public boolean isIbmMqMaxSharingConversationsEnabled() { + return isEnabled("ibm.mq.max.sharing.conversations"); + } + + public boolean isIbmMqCurrentSharingConversationsEnabled() { + return isEnabled("ibm.mq.current.sharing.conversations"); + } + + public boolean isIbmMqByteReceivedEnabled() { + return isEnabled("ibm.mq.byte.received"); + } + + public boolean isIbmMqByteSentEnabled() { + return isEnabled("ibm.mq.byte.sent"); + } + + public boolean isIbmMqBuffersReceivedEnabled() { + return isEnabled("ibm.mq.buffers.received"); + } + + public boolean isIbmMqBuffersSentEnabled() { + return isEnabled("ibm.mq.buffers.sent"); + } + + public boolean isIbmMqMessageCountEnabled() { + return isEnabled("ibm.mq.message.count"); + } + + public boolean isIbmMqOpenInputCountEnabled() { + return isEnabled("ibm.mq.open.input.count"); + } + + public boolean isIbmMqOpenOutputCountEnabled() { + return isEnabled("ibm.mq.open.output.count"); + } + + public boolean isIbmMqHighQueueDepthEnabled() { + return isEnabled("ibm.mq.high.queue.depth"); + } + + public boolean isIbmMqServiceIntervalEnabled() { + return isEnabled("ibm.mq.service.interval"); + } + + public boolean isIbmMqQueueDepthFullEventEnabled() { + return isEnabled("ibm.mq.queue.depth.full.event"); + } + + public boolean isIbmMqQueueDepthHighEventEnabled() { + return isEnabled("ibm.mq.queue.depth.high.event"); + } + + public boolean isIbmMqQueueDepthLowEventEnabled() { + return isEnabled("ibm.mq.queue.depth.low.event"); + } + + public boolean isIbmMqUncommittedMessagesEnabled() { + return isEnabled("ibm.mq.uncommitted.messages"); + } + + public boolean isIbmMqOldestMsgAgeEnabled() { + return isEnabled("ibm.mq.oldest.msg.age"); + } + + public boolean isIbmMqCurrentMaxQueueFilesizeEnabled() { + return isEnabled("ibm.mq.current.max.queue.filesize"); + } + + public boolean isIbmMqCurrentQueueFilesizeEnabled() { + return isEnabled("ibm.mq.current.queue.filesize"); + } + + public boolean isIbmMqInstancesPerClientEnabled() { + return isEnabled("ibm.mq.instances.per.client"); + } + + public boolean isIbmMqMessageDeqCountEnabled() { + return isEnabled("ibm.mq.message.deq.count"); + } + + public boolean isIbmMqMessageEnqCountEnabled() { + return isEnabled("ibm.mq.message.enq.count"); + } + + public boolean isIbmMqQueueDepthEnabled() { + return isEnabled("ibm.mq.queue.depth"); + } + + public boolean isIbmMqServiceIntervalEventEnabled() { + return isEnabled("ibm.mq.service.interval.event"); + } + + public boolean isIbmMqReusableLogSizeEnabled() { + return isEnabled("ibm.mq.reusable.log.size"); + } + + public boolean isIbmMqManagerActiveChannelsEnabled() { + return isEnabled("ibm.mq.manager.active.channels"); + } + + public boolean isIbmMqRestartLogSizeEnabled() { + return isEnabled("ibm.mq.restart.log.size"); + } + + public boolean isIbmMqMaxQueueDepthEnabled() { + return isEnabled("ibm.mq.max.queue.depth"); + } + + public boolean isIbmMqOnqtimeShortPeriodEnabled() { + return isEnabled("ibm.mq.onqtime.short_period"); + } + + public boolean isIbmMqOnqtimeLongPeriodEnabled() { + return isEnabled("ibm.mq.onqtime.long_period"); + } + + public boolean isIbmMqMessageReceivedCountEnabled() { + return isEnabled("ibm.mq.message.received.count"); + } + + public boolean isIbmMqMessageSentCountEnabled() { + return isEnabled("ibm.mq.message.sent.count"); + } + + public boolean isIbmMqMaxInstancesEnabled() { + return isEnabled("ibm.mq.max.instances"); + } + + public boolean isIbmMqConnectionCountEnabled() { + return isEnabled("ibm.mq.connection.count"); + } + + public boolean isIbmMqManagerStatusEnabled() { + return isEnabled("ibm.mq.manager.status"); + } + + public boolean isIbmMqHeartbeatEnabled() { + return isEnabled("ibm.mq.heartbeat"); + } + + public boolean isIbmMqArchiveLogSizeEnabled() { + return isEnabled("ibm.mq.archive.log.size"); + } + + public boolean isIbmMqManagerMaxActiveChannelsEnabled() { + return isEnabled("ibm.mq.manager.max.active.channels"); + } + + public boolean isIbmMqManagerStatisticsIntervalEnabled() { + return isEnabled("ibm.mq.manager.statistics.interval"); + } + + public boolean isIbmMqPublishCountEnabled() { + return isEnabled("ibm.mq.publish.count"); + } + + public boolean isIbmMqSubscriptionCountEnabled() { + return isEnabled("ibm.mq.subscription.count"); + } + + public boolean isIbmMqListenerStatusEnabled() { + return isEnabled("ibm.mq.listener.status"); + } + + public boolean isIbmMqUnauthorizedEventEnabled() { + return isEnabled("ibm.mq.unauthorized.event"); + } + + public boolean isIbmMqManagerMaxHandlesEnabled() { + return isEnabled("ibm.mq.manager.max.handles"); + } + + private boolean isEnabled(String key) { + Object metricInfo = config.get(key); + if (!(metricInfo instanceof Map)) { + return false; + } + Object enabled = ((Map) metricInfo).get("enabled"); + if (enabled instanceof Boolean) { + return (Boolean) enabled; + } + return false; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java new file mode 100644 index 000000000..44afdb28a --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQRC_SELECTOR_ERROR; +import static com.ibm.mq.constants.CMQCFC.MQRCCF_CHL_STATUS_NOT_FOUND; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_START_TIME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_JOB_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for channel metric collection. */ +public final class ChannelMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(ChannelMetricsCollector.class); + + private final LongGauge activeChannelsGauge; + private final LongGauge channelStatusGauge; + private final LongGauge messageCountGauge; + private final LongGauge byteSentGauge; + private final LongGauge byteReceivedGauge; + private final LongGauge buffersSentGauge; + private final LongGauge buffersReceivedGauge; + private final LongGauge currentSharingConvsGauge; + private final LongGauge maxSharingConvsGauge; + + /* + * The Channel Status values are mentioned here http://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.dev.doc/q090880_.htm + */ + public ChannelMetricsCollector(Meter meter) { + this.activeChannelsGauge = Metrics.createIbmMqManagerActiveChannels(meter); + this.channelStatusGauge = Metrics.createIbmMqStatus(meter); + this.messageCountGauge = Metrics.createIbmMqMessageCount(meter); + this.byteSentGauge = Metrics.createIbmMqByteSent(meter); + this.byteReceivedGauge = Metrics.createIbmMqByteReceived(meter); + this.buffersSentGauge = Metrics.createIbmMqBuffersSent(meter); + this.buffersReceivedGauge = Metrics.createIbmMqBuffersReceived(meter); + this.currentSharingConvsGauge = Metrics.createIbmMqCurrentSharingConversations(meter); + this.maxSharingConvsGauge = Metrics.createIbmMqMaxSharingConversations(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_CHANNEL_STATUS"); + long entryTime = System.currentTimeMillis(); + + int[] attrs = + new int[] { + CMQCFC.MQCACH_CHANNEL_NAME, + CMQCFC.MQCACH_CONNECTION_NAME, + CMQCFC.MQIACH_CHANNEL_TYPE, + CMQCFC.MQIACH_MSGS, + CMQCFC.MQIACH_CHANNEL_STATUS, + CMQCFC.MQIACH_BYTES_SENT, + CMQCFC.MQIACH_BYTES_RECEIVED, + CMQCFC.MQIACH_BUFFERS_SENT, + CMQCFC.MQIACH_BUFFERS_RECEIVED, + CMQCFC.MQIACH_CURRENT_SHARING_CONVS, + CMQCFC.MQIACH_MAX_SHARING_CONVS, + CMQCFC.MQCACH_CHANNEL_START_DATE, + CMQCFC.MQCACH_CHANNEL_START_TIME, + CMQCFC.MQCACH_MCA_JOB_NAME + }; + if (logger.isDebugEnabled()) { + logger.debug( + "Attributes being sent along PCF agent request to query channel metrics: {}", + Arrays.toString(attrs)); + } + + Set channelGenericNames = context.getChannelIncludeFilterNames(); + + // + // The MQCMD_INQUIRE_CHANNEL_STATUS command queries the current operational status of channels. + // This includes information about whether a channel is running, stopped, or in another state, + // as well as details about the channel’s performance and usage. + List activeChannels = new ArrayList<>(); + for (String channelGenericName : channelGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS); + request.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, channelGenericName); + request.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, CMQC.MQOT_CURRENT_CHANNEL); + request.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_ATTRS, attrs); + try { + logger.debug( + "sending PCF agent request to query metrics for generic channel {}", + channelGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + channelGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("channel") + .excluding(context.getChannelExcludeFilters()) + .withResourceExtractor(MessageBuddy::channelName) + .filter(response); + + for (PCFMessage message : messages) { + String channelName = MessageBuddy.channelName(message); + String channelType = MessageBuddy.channelType(message); + long channelStartTime = MessageBuddy.channelStartTime(message); + String jobName = MessageBuddy.jobName(message); + + logger.debug("Pulling out metrics for channel name {}", channelName); + updateMetrics( + context, + message, + channelName, + channelType, + channelStartTime, + jobName, + activeChannels); + } + } catch (PCFException pcfe) { + if (pcfe.getReason() == MQRCCF_CHL_STATUS_NOT_FOUND) { + String errorMsg = "Channel- " + channelGenericName + " :"; + errorMsg += + "Could not collect channel information as channel is stopped or inactive: Reason '3065'\n"; + errorMsg += + "If the channel type is MQCHT_RECEIVER, MQCHT_SVRCONN or MQCHT_CLUSRCVR, then the only action is to enable the channel, not start it."; + logger.error(errorMsg, pcfe); + } else if (pcfe.getReason() == MQRC_SELECTOR_ERROR) { + logger.error( + "Invalid metrics passed while collecting channel metrics, check config.yaml: Reason '2067'", + pcfe); + } else { + logger.error(pcfe.getMessage(), pcfe); + } + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting metrics for channel " + channelGenericName, + e); + } + } + + logger.info( + "Active Channels in queueManager {} are {}", context.getQueueManagerName(), activeChannels); + activeChannelsGauge.set( + activeChannels.size(), Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName())); + + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all channels is {} milliseconds", exitTime); + } + + private void updateMetrics( + MetricsCollectorContext context, + PCFMessage message, + String channelName, + String channelType, + long channelStartTime, + String jobName, + List activeChannels) + throws PCFException { + Attributes attributes = + Attributes.builder() + .put(IBM_MQ_CHANNEL_NAME, channelName) + .put(IBM_MQ_CHANNEL_TYPE, channelType) + .put(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()) + .put(IBM_MQ_CHANNEL_START_TIME, channelStartTime) + .put(IBM_MQ_JOB_NAME, jobName) + .build(); + if (context.getMetricsConfig().isIbmMqMessageCountEnabled()) { + int received = message.getIntParameterValue(CMQCFC.MQIACH_MSGS); + messageCountGauge.set(received, attributes); + } + int status = message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_STATUS); + if (context.getMetricsConfig().isIbmMqStatusEnabled()) { + channelStatusGauge.set(status, attributes); + } + // We follow the definition of active channel as documented in + // https://www.ibm.com/docs/en/ibm-mq/9.2.x?topic=states-current-active + if (status != CMQCFC.MQCHS_RETRYING + && status != CMQCFC.MQCHS_STOPPED + && status != CMQCFC.MQCHS_STARTING) { + activeChannels.add(channelName); + } + if (context.getMetricsConfig().isIbmMqByteSentEnabled()) { + byteSentGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BYTES_SENT), attributes); + } + if (context.getMetricsConfig().isIbmMqByteReceivedEnabled()) { + byteReceivedGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BYTES_RECEIVED), attributes); + } + if (context.getMetricsConfig().isIbmMqBuffersSentEnabled()) { + buffersSentGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BUFFERS_SENT), attributes); + } + if (context.getMetricsConfig().isIbmMqBuffersReceivedEnabled()) { + buffersReceivedGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_BUFFERS_RECEIVED), attributes); + } + if (context.getMetricsConfig().isIbmMqCurrentSharingConversationsEnabled()) { + int currentSharingConvs = 0; + if (message.getParameter(CMQCFC.MQIACH_CURRENT_SHARING_CONVS) != null) { + currentSharingConvs = message.getIntParameterValue(CMQCFC.MQIACH_CURRENT_SHARING_CONVS); + } + currentSharingConvsGauge.set(currentSharingConvs, attributes); + } + if (context.getMetricsConfig().isIbmMqMaxSharingConversationsEnabled()) { + int maxSharingConvs = 0; + if (message.getParameter(CMQCFC.MQIACH_MAX_SHARING_CONVS) != null) { + maxSharingConvs = message.getIntParameterValue(CMQCFC.MQIACH_MAX_SHARING_CONVS); + } + maxSharingConvsGauge.set(maxSharingConvs, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java new file mode 100644 index 000000000..668ba2ac0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +public enum FilterType { + STARTSWITH, + EQUALS, + ENDSWITH, + CONTAINS, + NONE +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java new file mode 100644 index 000000000..b0dc7286c --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java @@ -0,0 +1,149 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for channel inquiry metric collection. */ +public final class InquireChannelCmdCollector implements Consumer { + + public static final Logger logger = LoggerFactory.getLogger(InquireChannelCmdCollector.class); + private final LongGauge maxClientsGauge; + private final LongGauge instancesPerClientGauge; + private final LongGauge messageRetryCountGauge; + private final LongGauge messageReceivedCountGauge; + private final LongGauge messageSentCountGauge; + + public InquireChannelCmdCollector(Meter meter) { + this.maxClientsGauge = Metrics.createIbmMqMaxInstances(meter); + this.instancesPerClientGauge = Metrics.createIbmMqInstancesPerClient(meter); + this.messageRetryCountGauge = Metrics.createIbmMqMessageRetryCount(meter); + this.messageReceivedCountGauge = Metrics.createIbmMqMessageReceivedCount(meter); + this.messageSentCountGauge = Metrics.createIbmMqMessageSentCount(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + + Set channelGenericNames = context.getChannelIncludeFilterNames(); + + for (String channelGenericName : channelGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_CHANNEL); + request.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, channelGenericName); + request.addParameter( + new MQCFIL(MQConstants.MQIACF_CHANNEL_ATTRS, new int[] {MQConstants.MQIACF_ALL})); + try { + logger.debug( + "sending PCF agent request to query metrics for generic channel {}", + channelGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + channelGenericName, + endTime); + if (response.isEmpty()) { + logger.warn("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("channel") + .excluding(context.getChannelExcludeFilters()) + .withResourceExtractor(MessageBuddy::channelName) + .filter(response); + + for (PCFMessage message : messages) { + String channelName = MessageBuddy.channelName(message); + String channelType = MessageBuddy.channelType(message); + logger.debug("Pulling out metrics for channel name {}", channelName); + updateMetrics(message, channelName, channelType, context); + } + } catch (PCFException pcfe) { + if (pcfe.getReason() == MQConstants.MQRCCF_CHL_STATUS_NOT_FOUND) { + String errorMsg = "Channel- " + channelGenericName + " :"; + errorMsg += + "Could not collect channel information as channel is stopped or inactive: Reason '3065'\n"; + errorMsg += + "If the channel type is MQCHT_RECEIVER, MQCHT_SVRCONN or MQCHT_CLUSRCVR, then the only action is to enable the channel, not start it."; + logger.error(errorMsg, pcfe); + } else if (pcfe.getReason() == MQConstants.MQRC_SELECTOR_ERROR) { + logger.error( + "Invalid metrics passed while collecting channel metrics, check config.yaml: Reason '2067'", + pcfe); + } + logger.error(pcfe.getMessage(), pcfe); + } catch (Exception e) { + logger.error( + "Unexpected error while collecting metrics for channel " + channelGenericName, e); + } + } + + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all channels is {} milliseconds", exitTime); + } + + private void updateMetrics( + PCFMessage message, String channelName, String channelType, MetricsCollectorContext context) + throws PCFException { + Attributes attributes = + Attributes.builder() + .put(IBM_MQ_CHANNEL_NAME, channelName) + .put(IBM_MQ_CHANNEL_TYPE, channelType) + .put(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()) + .build(); + if (context.getMetricsConfig().isIbmMqMaxInstancesEnabled() + && message.getParameter(CMQCFC.MQIACH_MAX_INSTANCES) != null) { + this.maxClientsGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_MAX_INSTANCES), attributes); + } + if (context.getMetricsConfig().isIbmMqInstancesPerClientEnabled() + && message.getParameter(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT) != null) { + this.instancesPerClientGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT), attributes); + } + if (context.getMetricsConfig().isIbmMqMessageRetryCountEnabled()) { + int count = 0; + if (message.getParameter(CMQCFC.MQIACH_MR_COUNT) != null) { + count = message.getIntParameterValue(CMQCFC.MQIACH_MR_COUNT); + } + this.messageRetryCountGauge.set(count, attributes); + } + if (context.getMetricsConfig().isIbmMqInstancesPerClientEnabled()) { + int received = 0; + if (message.getParameter(CMQCFC.MQIACH_MSGS_RECEIVED) != null) { + received = message.getIntParameterValue(CMQCFC.MQIACH_MSGS_RECEIVED); + } + this.messageReceivedCountGauge.set(received, attributes); + } + if (context.getMetricsConfig().isIbmMqMessageSentCountEnabled()) { + int sent = 0; + if (message.getParameter(CMQCFC.MQIACH_MSGS_SENT) != null) { + sent = message.getIntParameterValue(CMQCFC.MQIACH_MSGS_SENT); + } + this.messageSentCountGauge.set(sent, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java new file mode 100644 index 000000000..16164b46d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class InquireQCmdCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(InquireQCmdCollector.class); + + static final int[] ATTRIBUTES = + new int[] { + CMQC.MQCA_Q_NAME, + CMQC.MQIA_USAGE, + CMQC.MQIA_Q_TYPE, + CMQC.MQIA_CURRENT_Q_DEPTH, + CMQC.MQIA_MAX_Q_DEPTH, + CMQC.MQIA_OPEN_INPUT_COUNT, + CMQC.MQIA_OPEN_OUTPUT_COUNT, + CMQC.MQIA_Q_SERVICE_INTERVAL, + CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT + }; + + static final String COMMAND = "MQCMD_INQUIRE_Q"; + private final QueueCollectionBuddy queueBuddy; + + public InquireQCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command {}", COMMAND); + long entryTime = System.currentTimeMillis(); + + logger.debug( + "Attributes being sent along PCF agent request to query queue metrics: {} for command {}", + Arrays.toString(ATTRIBUTES), + COMMAND); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_INQUIRE_Q is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q087810_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_ALL); + request.addParameter(CMQCFC.MQIACF_Q_ATTRS, ATTRIBUTES); + + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command {}", + exitTime, + COMMAND); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java new file mode 100644 index 000000000..3580c02b1 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The InquireQStatusCmdCollector class is responsible for collecting and publishing queue metrics + * using the IBM MQ command `MQCMD_INQUIRE_Q_STATUS`. It extends the QueueMetricsCollector class and + * implements the Runnable interface, enabling execution within a separate thread. + * + *

This class interacts with PCF (Programmable Command Formats) messages to query queue metrics + * based on the configuration provided. It retrieves status information about a queue, such as: • + * The number of messages on the queue • Open handles (how many apps have it open) • Whether the + * queue is in use for input/output • Last get/put timestamps • And other real-time statistics + * + *

Thread Safety: This class is thread-safe, as it operates independently with state shared only + * through immutable or synchronized structures where necessary. + * + *

Usage: - Instantiate this class by providing an existing QueueMetricsCollector instance, a map + * of metrics to report, and shared state. - Invoke the run method to execute the queue metrics + * collection process. + */ +final class InquireQStatusCmdCollector implements Consumer { + + static final int[] ATTRIBUTES = + new int[] { + CMQC.MQCA_Q_NAME, + CMQCFC.MQIACF_CUR_Q_FILE_SIZE, + CMQCFC.MQIACF_CUR_MAX_FILE_SIZE, + CMQCFC.MQIACF_OLDEST_MSG_AGE, + CMQCFC.MQIACF_UNCOMMITTED_MSGS, + CMQCFC.MQIACF_Q_TIME_INDICATOR, + CMQC.MQIA_CURRENT_Q_DEPTH, + }; + + private static final Logger logger = LoggerFactory.getLogger(InquireQStatusCmdCollector.class); + + private final QueueCollectionBuddy queueBuddy; + + InquireQStatusCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_Q_STATUS"); + long entryTime = System.currentTimeMillis(); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_INQUIRE_Q_STATUS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q087880_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_STATUS); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + request.addParameter(CMQCFC.MQIACF_Q_STATUS_ATTRS, ATTRIBUTES); + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command MQCMD_INQUIRE_Q_STATUS", + exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java new file mode 100644 index 000000000..5cdf0c52f --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for queue metric collection. */ +public final class InquireQueueManagerCmdCollector implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(InquireQueueManagerCmdCollector.class); + private final LongGauge statisticsIntervalGauge; + + public InquireQueueManagerCmdCollector(Meter meter) { + this.statisticsIntervalGauge = Metrics.createIbmMqManagerStatisticsInterval(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + logger.debug( + "publishMetrics entry time for queuemanager {} is {} milliseconds", + context.getQueueManagerName(), + entryTime); + // CMQCFC.MQCMD_INQUIRE_Q_MGR is 2 + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_MGR); + // request.addParameter(CMQC.MQCA_Q_MGR_NAME, "*"); + // CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS is 1001 + request.addParameter( + new MQCFIL(MQConstants.MQIACF_Q_MGR_ATTRS, new int[] {MQConstants.MQIACF_ALL})); + try { + // Note that agent.send() method is synchronized + logger.debug( + "sending PCF agent request to query queuemanager {}", context.getQueueManagerName()); + long startTime = System.currentTimeMillis(); + List responses = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queuemanager metrics query response for {} received in {} milliseconds", + context.getQueueManagerName(), + endTime); + if (responses.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is either null or empty"); + return; + } + if (context.getMetricsConfig().isIbmMqManagerStatisticsIntervalEnabled()) { + int interval = responses.get(0).getIntParameterValue(CMQC.MQIA_STATISTICS_INTERVAL); + statisticsIntervalGauge.set( + interval, Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName())); + } + } catch (Exception e) { + logger.error("Error collecting QueueManagerCmd metrics", e); + throw new IllegalStateException(e); + } finally { + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for queuemanager is {} milliseconds", exitTime); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java new file mode 100644 index 000000000..bb3812565 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class InquireTStatusCmdCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(InquireTStatusCmdCollector.class); + + private final LongGauge publishCountGauge; + private final LongGauge subscriptionCountGauge; + + public InquireTStatusCmdCollector(Meter meter) { + this.publishCountGauge = Metrics.createIbmMqPublishCount(meter); + this.subscriptionCountGauge = Metrics.createIbmMqSubscriptionCount(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_TOPIC_STATUS"); + long entryTime = System.currentTimeMillis(); + + Set topicGenericNames = context.getTopicIncludeFilterNames(); + // to query the current status of topics, which is essential for monitoring and managing the + // publish/subscribe environment in IBM MQ. + for (String topicGenericName : topicGenericNames) { + // Request: + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088140_.htm + // list of all metrics extracted through MQCMD_INQUIRE_TOPIC_STATUS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088150_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS); + request.addParameter(CMQC.MQCA_TOPIC_STRING, topicGenericName); + + try { + processPcfRequestAndPublishQMetrics(context, topicGenericName, request); + } catch (PCFException pcfe) { + logger.error( + "PCFException caught while collecting metric for Queue: {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicGenericName, + pcfe); + PCFMessage[] msgs = (PCFMessage[]) pcfe.exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + // Don't throw exception as it will stop queue metric colloection + } catch (Exception mqe) { + logger.error("MQException caught", mqe); + // Dont throw exception as it will stop queuemetric colloection + } + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command MQCMD_INQUIRE_TOPIC_STATUS", + exitTime); + } + + private void processPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, String topicGenericName, PCFMessage request) + throws IOException, MQDataException { + logger.debug( + "sending PCF agent request to topic metrics for generic topic {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent topic metrics query response for generic topic {} for command MQCMD_INQUIRE_TOPIC_STATUS received in {} milliseconds", + topicGenericName, + endTime); + if (response.isEmpty()) { + logger.debug( + "Unexpected error while PCFMessage.send() for command MQCMD_INQUIRE_TOPIC_STATUS, response is either null or empty"); + return; + } + + List messages = + MessageFilter.ofKind("topic") + .excluding(context.getTopicExcludeFilters()) + .withResourceExtractor(MessageBuddy::topicName) + .filter(response); + + for (PCFMessage message : messages) { + String topicName = MessageBuddy.topicName(message); + logger.debug( + "Pulling out metrics for topic name {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicName); + extractMetrics(context, message, topicName); + } + } + + private void extractMetrics( + MetricsCollectorContext context, PCFMessage pcfMessage, String topicString) + throws PCFException { + Attributes attributes = + Attributes.of( + MESSAGING_DESTINATION_NAME, + topicString, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName()); + if (context.getMetricsConfig().isIbmMqPublishCountEnabled()) { + int publisherCount = 0; + if (pcfMessage.getParameter(CMQC.MQIA_PUB_COUNT) != null) { + publisherCount = pcfMessage.getIntParameterValue(CMQC.MQIA_PUB_COUNT); + } + publishCountGauge.set(publisherCount, attributes); + } + if (context.getMetricsConfig().isIbmMqSubscriptionCountEnabled()) { + int subscriberCount = 0; + if (pcfMessage.getParameter(CMQC.MQIA_SUB_COUNT) != null) { + subscriberCount = pcfMessage.getIntParameterValue(CMQC.MQIA_SUB_COUNT); + } + subscriptionCountGauge.set(subscriberCount, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java new file mode 100644 index 000000000..a7bf3d48d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java @@ -0,0 +1,113 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_LISTENER_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ListenerMetricsCollector is a specialized implementation of the MetricsCollector that is + * responsible for collecting and publishing metrics related to IBM MQ Listeners. + * + *

This class interacts with PCFMessageAgent to query metrics for specific listeners, applies + * "include:" and "exclude:" listenerFilters defined in config yaml, and uses MetricWriteHelper to + * publish the collected metrics in the required format. + * + *

Key functionalities include: • query using PCF Command: MQCMD_INQUIRE_LISTENER_STATUS to get + * the status of one or more listeners on a queue manager. • retrieve tcp/ip listeners runtime + * information such as: - listener is running or stopped - port number and transport type - last + * error codes - associated command server • + * + *

It utilizes WMQMetricOverride to map metrics from the configuration to their IBM MQ constants. + */ +public final class ListenerMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(ListenerMetricsCollector.class); + private final LongGauge listenerStatusGauge; + + public ListenerMetricsCollector(Meter meter) { + this.listenerStatusGauge = Metrics.createIbmMqListenerStatus(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + + int[] attrs = new int[] {CMQCFC.MQCACH_LISTENER_NAME, CMQCFC.MQIACH_LISTENER_STATUS}; + logger.debug( + "Attributes being sent along PCF agent request to query channel metrics: " + + Arrays.toString(attrs)); + + Set listenerGenericNames = context.getListenerIncludeFilterNames(); + for (String listenerGenericName : listenerGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS); + request.addParameter(CMQCFC.MQCACH_LISTENER_NAME, listenerGenericName); + request.addParameter(CMQCFC.MQIACF_LISTENER_STATUS_ATTRS, attrs); + try { + logger.debug( + "sending PCF agent request to query metrics for generic listener {}", + listenerGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent listener metrics query response for generic listener {} received in {} milliseconds", + listenerGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("listener") + .excluding(context.getListenerExcludeFilters()) + .withResourceExtractor(MessageBuddy::listenerName) + .filter(response); + + for (PCFMessage message : messages) { + String listenerName = MessageBuddy.listenerName(message); + logger.debug("Pulling out metrics for listener name {}", listenerName); + updateMetrics(message, listenerName, context); + } + } catch (Exception e) { + logger.error( + "Unexpected error while collecting metrics for listener " + listenerGenericName, e); + } + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all listener is {} milliseconds", exitTime); + } + + private void updateMetrics( + PCFMessage message, String listenerName, MetricsCollectorContext context) + throws PCFException { + if (context.getMetricsConfig().isIbmMqListenerStatusEnabled()) { + int status = message.getIntParameterValue(CMQCFC.MQIACH_LISTENER_STATUS); + listenerStatusGauge.set( + status, + Attributes.of( + IBM_MQ_LISTENER_NAME, + listenerName, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName())); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java new file mode 100644 index 000000000..6ed74dad9 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.CMQXC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.time.Instant; + +public class MessageBuddy { + + private MessageBuddy() {} + + static String channelName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_NAME).trim(); + } + + static String channelType(PCFMessage message) throws PCFException { + switch (message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_TYPE)) { + case CMQXC.MQCHT_SENDER: + return "sender"; + case CMQXC.MQCHT_SERVER: + return "server"; + case CMQXC.MQCHT_RECEIVER: + return "receiver"; + case CMQXC.MQCHT_REQUESTER: + return "requester"; + case CMQXC.MQCHT_SVRCONN: + return "server-connection"; + case CMQXC.MQCHT_CLNTCONN: + return "client-connection"; + case CMQXC.MQCHT_CLUSRCVR: + return "cluster-receiver"; + case CMQXC.MQCHT_CLUSSDR: + return "cluster-sender"; + case CMQXC.MQCHT_MQTT: + return "mqtt"; + case CMQXC.MQCHT_AMQP: + return "amqp"; + default: + throw new IllegalArgumentException( + "Unsupported channel type: " + + message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_TYPE)); + } + } + + static String topicName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQC.MQCA_TOPIC_STRING).trim(); + } + + public static String listenerName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_LISTENER_NAME).trim(); + } + + public static String queueName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQC.MQCA_Q_NAME).trim(); + } + + public static long channelStartTime(PCFMessage message) throws PCFException { + String date = message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_START_DATE).trim(); + String time = message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_START_TIME).trim(); + + Instant parsed = Instant.parse(date + "T" + time.replaceAll("\\.", ":") + "Z"); + return parsed.getEpochSecond(); + } + + public static String jobName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_MCA_JOB_NAME).trim(); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java new file mode 100644 index 000000000..531c217b0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.ibm.mq.config.ExcludeFilters; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Helps to consolidate repeated exclude/filtering logic. */ +class MessageFilter { + + private static final Logger logger = LoggerFactory.getLogger(MessageFilter.class); + + private final String kind; + private final Collection filters; + private final ResourceExtractor extractor; + + private MessageFilter( + String kind, Collection filters, ResourceExtractor extractor) { + this.kind = kind; + this.filters = filters; + this.extractor = extractor; + } + + static MessageFilterBuilder ofKind(String kind) { + return new MessageFilterBuilder(kind); + } + + public List filter(List messages) throws PCFException { + List result = new ArrayList<>(); + for (PCFMessage message : messages) { + String resourceName = extractor.apply(message); + if (ExcludeFilters.isExcluded(resourceName, filters)) { + logger.debug("{} name = {} is excluded.", kind, resourceName); + } else { + result.add(message); + } + } + return result; + } + + static class MessageFilterBuilder { + + private final String kind; + private Set filters = new HashSet<>(); + + public MessageFilterBuilder(String kind) { + this.kind = kind; + } + + @CanIgnoreReturnValue + public MessageFilterBuilder excluding(Set filters) { + this.filters = new HashSet<>(filters); + return this; + } + + public MessageFilter withResourceExtractor(ResourceExtractor extractor) { + return new MessageFilter(kind, filters, extractor); + } + } + + interface ResourceExtractor { + // Ugh, exceptions everywhere, huh? + String apply(PCFMessage message) throws PCFException; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java new file mode 100644 index 000000000..dab85748e --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static java.util.Collections.emptyList; + +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.ExcludeFilters; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.jetbrains.annotations.NotNull; + +/** + * A temporary bundle to contain the collaborators of the original MetricsCollector base class until + * we can finish unwinding things. When done and there are no longer usages of MetricsCollector, we + * could consider renaming this. + */ +@Immutable +public final class MetricsCollectorContext { + + private final QueueManager queueManager; + private final PCFMessageAgent agent; + private final MQQueueManager mqQueueManager; + private final MetricsConfig metricsConfig; + + public MetricsCollectorContext( + QueueManager queueManager, + PCFMessageAgent agent, + MQQueueManager mqQueueManager, + MetricsConfig metricsConfig) { + this.queueManager = queueManager; + this.agent = agent; + this.mqQueueManager = mqQueueManager; + this.metricsConfig = metricsConfig; + } + + Set getChannelIncludeFilterNames() { + return queueManager.getChannelFilters().getInclude(); + } + + Set getChannelExcludeFilters() { + return queueManager.getChannelFilters().getExclude(); + } + + Set getListenerIncludeFilterNames() { + return queueManager.getListenerFilters().getInclude(); + } + + Set getListenerExcludeFilters() { + return queueManager.getListenerFilters().getExclude(); + } + + Set getTopicIncludeFilterNames() { + return queueManager.getTopicFilters().getInclude(); + } + + Set getTopicExcludeFilters() { + return queueManager.getTopicFilters().getExclude(); + } + + Set getQueueIncludeFilterNames() { + return queueManager.getQueueFilters().getInclude(); + } + + Set getQueueExcludeFilters() { + return queueManager.getQueueFilters().getExclude(); + } + + @NotNull + List send(PCFMessage request) throws IOException, MQDataException { + PCFMessage[] result = agent.send(request); + return result == null ? emptyList() : Arrays.asList(result); + } + + String getQueueManagerName() { + return queueManager.getName(); + } + + QueueManager getQueueManager() { + return queueManager; + } + + public MQQueueManager getMqQueueManager() { + return mqQueueManager; + } + + public MetricsConfig getMetricsConfig() { + return metricsConfig; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java new file mode 100644 index 000000000..db1d4a254 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java @@ -0,0 +1,131 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Captures metrics from events logged to the queue manager performance event queue. +public final class PerformanceEventQueueCollector implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(PerformanceEventQueueCollector.class); + private final LongCounter fullQueueDepthCounter; + private final LongCounter highQueueDepthCounter; + private final LongCounter lowQueueDepthCounter; + + public PerformanceEventQueueCollector(Meter meter) { + this.fullQueueDepthCounter = Metrics.createIbmMqQueueDepthFullEvent(meter); + this.highQueueDepthCounter = Metrics.createIbmMqQueueDepthHighEvent(meter); + this.lowQueueDepthCounter = Metrics.createIbmMqQueueDepthLowEvent(meter); + } + + private void readEvents(MetricsCollectorContext context, String performanceEventsQueueName) + throws Exception { + + MQQueue queue = null; + int counter = 0; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + queue = + context.getMqQueueManager().accessQueue(performanceEventsQueueName, queueAccessOptions); + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + logger.debug("Start reading events from performance queue {}", performanceEventsQueueName); + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage receivedMsg = new PCFMessage(message); + incrementCounterByEventType(context, receivedMsg); + counter++; + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + logger.debug("Read {} events from performance queue {}", counter, performanceEventsQueueName); + } + + private void incrementCounterByEventType(MetricsCollectorContext context, PCFMessage receivedMsg) + throws PCFException { + String queueName = receivedMsg.getStringParameterValue(CMQC.MQCA_BASE_OBJECT_NAME).trim(); + Attributes attributes = + Attributes.of( + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName(), + MESSAGING_DESTINATION_NAME, + queueName); + switch (receivedMsg.getReason()) { + case CMQC.MQRC_Q_FULL: + if (context.getMetricsConfig().isIbmMqQueueDepthFullEventEnabled()) { + fullQueueDepthCounter.add(1, attributes); + } + break; + case CMQC.MQRC_Q_DEPTH_HIGH: + if (context.getMetricsConfig().isIbmMqQueueDepthHighEventEnabled()) { + highQueueDepthCounter.add(1, attributes); + } + break; + case CMQC.MQRC_Q_DEPTH_LOW: + if (context.getMetricsConfig().isIbmMqQueueDepthLowEventEnabled()) { + lowQueueDepthCounter.add(1, attributes); + } + break; + default: + logger.debug("Unknown event reason {}", receivedMsg.getReason()); + } + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String performanceEventsQueueName = context.getQueueManager().getPerformanceEventsQueueName(); + logger.info( + "sending PCF agent request to read performance events from queue {}", + performanceEventsQueueName); + try { + readEvents(context, performanceEventsQueueName); + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting performance events for queue " + + performanceEventsQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for performance events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java new file mode 100644 index 000000000..50187adeb --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java @@ -0,0 +1,311 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQQT_ALIAS; +import static com.ibm.mq.constants.CMQC.MQQT_CLUSTER; +import static com.ibm.mq.constants.CMQC.MQQT_LOCAL; +import static com.ibm.mq.constants.CMQC.MQQT_MODEL; +import static com.ibm.mq.constants.CMQC.MQQT_REMOTE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.ibm.mq.metrics.Metrics.MIBY_TO_BYTES; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.MQCFIN; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFParameter; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A collaborator buddy of the queue collectors that helps them to send a message, process the + * response, and generate metrics. + */ +final class QueueCollectionBuddy { + private static final Logger logger = LoggerFactory.getLogger(QueueCollectionBuddy.class); + private final Map gauges = new HashMap<>(); + + private final QueueCollectorSharedState sharedState; + private final LongGauge onqtimeShort; + private final LongGauge onqtimeLong; + + @FunctionalInterface + private interface AllowedGauge { + void set(MetricsCollectorContext context, Integer value, Attributes attributes); + } + + private static AllowedGauge createAllowedGauge( + LongGauge gauge, Function allowed) { + return createAllowedGauge(gauge, allowed, Integer::longValue /*identity*/); + } + + private static AllowedGauge createAllowedGauge( + LongGauge gauge, + Function allowed, + Function unitMangler) { + return (context, val, attributes) -> { + if (allowed.apply(context.getMetricsConfig())) { + gauge.set(unitMangler.apply(val), attributes); + } + }; + } + + QueueCollectionBuddy(Meter meter, QueueCollectorSharedState sharedState) { + this.sharedState = sharedState; + gauges.put( + CMQC.MQIA_CURRENT_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqQueueDepth(meter), MetricsConfig::isIbmMqQueueDepthEnabled)); + gauges.put( + CMQC.MQIA_MAX_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqMaxQueueDepth(meter), MetricsConfig::isIbmMqMaxQueueDepthEnabled)); + gauges.put( + CMQC.MQIA_OPEN_INPUT_COUNT, + createAllowedGauge( + Metrics.createIbmMqOpenInputCount(meter), MetricsConfig::isIbmMqOpenInputCountEnabled)); + gauges.put( + CMQC.MQIA_OPEN_OUTPUT_COUNT, + createAllowedGauge( + Metrics.createIbmMqOpenOutputCount(meter), + MetricsConfig::isIbmMqOpenOutputCountEnabled)); + gauges.put( + CMQC.MQIA_Q_SERVICE_INTERVAL, + createAllowedGauge( + Metrics.createIbmMqServiceInterval(meter), + MetricsConfig::isIbmMqServiceIntervalEnabled)); + gauges.put( + CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT, + createAllowedGauge( + Metrics.createIbmMqServiceIntervalEvent(meter), + MetricsConfig::isIbmMqServiceIntervalEventEnabled)); + gauges.put( + CMQCFC.MQIACF_OLDEST_MSG_AGE, + createAllowedGauge( + Metrics.createIbmMqOldestMsgAge(meter), MetricsConfig::isIbmMqOldestMsgAgeEnabled)); + gauges.put( + CMQCFC.MQIACF_UNCOMMITTED_MSGS, + createAllowedGauge( + Metrics.createIbmMqUncommittedMessages(meter), + MetricsConfig::isIbmMqUncommittedMessagesEnabled)); + gauges.put( + CMQC.MQIA_MSG_DEQ_COUNT, + createAllowedGauge( + Metrics.createIbmMqMessageDeqCount(meter), + MetricsConfig::isIbmMqMessageDeqCountEnabled)); + gauges.put( + CMQC.MQIA_MSG_ENQ_COUNT, + createAllowedGauge( + Metrics.createIbmMqMessageEnqCount(meter), + MetricsConfig::isIbmMqMessageEnqCountEnabled)); + gauges.put( + CMQC.MQIA_HIGH_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqHighQueueDepth(meter), MetricsConfig::isIbmMqHighQueueDepthEnabled)); + gauges.put( + CMQCFC.MQIACF_CUR_Q_FILE_SIZE, + createAllowedGauge( + Metrics.createIbmMqCurrentQueueFilesize(meter), + MetricsConfig::isIbmMqCurrentQueueFilesizeEnabled, + MIBY_TO_BYTES)); + gauges.put( + CMQCFC.MQIACF_CUR_MAX_FILE_SIZE, + createAllowedGauge( + Metrics.createIbmMqCurrentMaxQueueFilesize(meter), + MetricsConfig::isIbmMqCurrentMaxQueueFilesizeEnabled, + MIBY_TO_BYTES)); + + this.onqtimeShort = Metrics.createIbmMqOnqtimeShortPeriod(meter); + this.onqtimeLong = Metrics.createIbmMqOnqtimeLongPeriod(meter); + } + + /** + * Sends a PCFMessage request, reads the response, and generates metrics from the response. It + * handles all exceptions. + */ + void processPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, PCFMessage request, String queueGenericName, int[] fields) { + try { + doProcessPcfRequestAndPublishQMetrics(context, request, queueGenericName, fields); + } catch (PCFException pcfe) { + logger.error( + "PCFException caught while collecting metric for Queue: {}", queueGenericName, pcfe); + if (pcfe.exceptionSource instanceof PCFMessage[]) { + PCFMessage[] msgs = (PCFMessage[]) pcfe.exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + } + if (pcfe.exceptionSource instanceof PCFMessage) { + PCFMessage msg = (PCFMessage) pcfe.exceptionSource; + logger.error(msg.toString()); + } + // Don't throw exception as it will stop queue metric collection + } catch (Exception mqe) { + logger.error("MQException caught", mqe); + // Don't throw exception as it will stop queue metric collection + } + } + + private void doProcessPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, PCFMessage request, String queueGenericName, int[] fields) + throws IOException, MQDataException { + logger.debug( + "sending PCF agent request to query metrics for generic queue {}", queueGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + queueGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("queue") + .excluding(context.getQueueExcludeFilters()) + .withResourceExtractor(MessageBuddy::queueName) + .filter(response); + + for (PCFMessage message : messages) { + handleMessage(context, message, fields); + } + } + + private void handleMessage(MetricsCollectorContext context, PCFMessage message, int[] fields) + throws PCFException { + String queueName = MessageBuddy.queueName(message); + String queueType = getQueueTypeFromName(message, queueName); + if (queueType == null) { + logger.info("Unable to determine queue type for queue name = {}", queueName); + return; + } + + logger.debug("Pulling out metrics for queue name {}", queueName); + getMetrics(context, message, queueName, queueType, fields); + } + + @Nullable + private String getQueueTypeFromName(PCFMessage message, String queueName) throws PCFException { + if (message.getParameterValue(CMQC.MQIA_Q_TYPE) == null) { + return sharedState.getType(queueName); + } + + String queueType = getQueueType(message); + sharedState.putQueueType(queueName, queueType); + return queueType; + } + + private static String getQueueType(PCFMessage message) throws PCFException { + String baseQueueType = getBaseQueueType(message); + return maybeAppendUsage(message, baseQueueType); + } + + private static String maybeAppendUsage(PCFMessage message, String baseQueueType) + throws PCFException { + if (message.getParameter(CMQC.MQIA_USAGE) == null) { + return baseQueueType; + } + switch (message.getIntParameterValue(CMQC.MQIA_USAGE)) { + case CMQC.MQUS_NORMAL: + return baseQueueType + "-normal"; + case CMQC.MQUS_TRANSMISSION: + return baseQueueType + "-transmission"; + default: + return baseQueueType; + } + } + + private static String getBaseQueueType(PCFMessage message) throws PCFException { + switch (message.getIntParameterValue(CMQC.MQIA_Q_TYPE)) { + case MQQT_LOCAL: + return "local"; + case MQQT_ALIAS: + return "alias"; + case MQQT_REMOTE: + return "remote"; + case MQQT_CLUSTER: + return "cluster"; + case MQQT_MODEL: + return "model"; + default: + logger.warn("Unknown type of queue {}", message.getIntParameterValue(CMQC.MQIA_Q_TYPE)); + return "unknown"; + } + } + + private void getMetrics( + MetricsCollectorContext context, + PCFMessage pcfMessage, + String queueName, + String queueType, + int[] fields) + throws PCFException { + + for (int field : fields) { + if (field == CMQC.MQCA_Q_NAME || field == CMQC.MQIA_USAGE || field == CMQC.MQIA_Q_TYPE) { + continue; + } + updateMetrics(context, pcfMessage, queueName, queueType, field); + } + } + + private void updateMetrics( + MetricsCollectorContext context, + PCFMessage pcfMessage, + String queueName, + String queueType, + int constantValue) + throws PCFException { + PCFParameter pcfParam = pcfMessage.getParameter(constantValue); + Attributes attributes = + Attributes.of( + MESSAGING_DESTINATION_NAME, + queueName, + IBM_MQ_QUEUE_TYPE, + queueType, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName()); + + if (pcfParam instanceof MQCFIN) { + AllowedGauge g = this.gauges.get(constantValue); + if (g == null) { + throw new IllegalArgumentException("Unknown constantValue " + constantValue); + } + int metricVal = pcfMessage.getIntParameterValue(constantValue); + g.set(context, metricVal, attributes); + } + if (pcfParam instanceof MQCFIL) { + int[] metricVals = pcfMessage.getIntListParameterValue(constantValue); + if (context.getMetricsConfig().isIbmMqOnqtimeShortPeriodEnabled()) { + onqtimeShort.set(metricVals[0], attributes); + } + if (context.getMetricsConfig().isIbmMqOnqtimeLongPeriodEnabled()) { + onqtimeLong.set(metricVals[1], attributes); + } + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java new file mode 100644 index 000000000..94fcc7788 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +final class QueueCollectorSharedState { + + private final ConcurrentHashMap queueNameToType = new ConcurrentHashMap<>(); + + QueueCollectorSharedState() {} + + public void putQueueType(String name, String value) { + queueNameToType.put(name, value); + } + + @Nullable + public String getType(String name) { + return queueNameToType.get(name); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java new file mode 100644 index 000000000..723e433cf --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.SERVICE_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.USER_NAME; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Reads queue manager events and counts them as metrics +public final class QueueManagerEventCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueManagerEventCollector.class); + private final LongCounter authorityEventCounter; + + public QueueManagerEventCollector(Meter meter) { + this.authorityEventCounter = Metrics.createIbmMqUnauthorizedEvent(meter); + } + + private void readEvents(MetricsCollectorContext context, String queueManagerEventsQueueName) + throws Exception { + + MQQueue queue = null; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + queue = + context.getMqQueueManager().accessQueue(queueManagerEventsQueueName, queueAccessOptions); + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage received = new PCFMessage(message); + if (received.getReason() == CMQC.MQRC_NOT_AUTHORIZED) { + + if (context.getMetricsConfig().isIbmMqUnauthorizedEventEnabled()) { + String username = received.getStringParameterValue(CMQCFC.MQCACF_USER_IDENTIFIER); + String applicationName = received.getStringParameterValue(CMQCFC.MQCACF_APPL_NAME); + authorityEventCounter.add( + 1, + Attributes.of( + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName(), + USER_NAME, + username, + SERVICE_NAME, + applicationName)); + } + } else { + logger.debug("Unknown event reason {}", received.getReason()); + } + + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String queueManagerEventsQueueName = context.getQueueManager().getQueueManagerEventsQueueName(); + logger.info( + "sending PCF agent request to read queue manager events from queue {}", + queueManagerEventsQueueName); + try { + readEvents(context, queueManagerEventsQueueName); + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting queue manager events for queue " + + queueManagerEventsQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for queue manager events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java new file mode 100644 index 000000000..2b3d57086 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.Metrics.MIBY_TO_BYTES; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for queue manager metric collection. */ +public final class QueueManagerMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueManagerMetricsCollector.class); + + private final LongGauge statusGauge; + private final LongGauge connectionCountGauge; + private final LongGauge restartLogSizeGauge; + private final LongGauge reuseLogSizeGauge; + private final LongGauge archiveLogSizeGauge; + private final LongGauge maxActiveChannelsGauge; + + public QueueManagerMetricsCollector(Meter meter) { + this.statusGauge = Metrics.createIbmMqManagerStatus(meter); + this.connectionCountGauge = Metrics.createIbmMqConnectionCount(meter); + this.restartLogSizeGauge = Metrics.createIbmMqRestartLogSize(meter); + this.reuseLogSizeGauge = Metrics.createIbmMqReusableLogSize(meter); + this.archiveLogSizeGauge = Metrics.createIbmMqArchiveLogSize(meter); + this.maxActiveChannelsGauge = Metrics.createIbmMqManagerMaxActiveChannels(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + logger.debug( + "publishMetrics entry time for queuemanager {} is {} milliseconds", + context.getQueueManagerName(), + entryTime); + // CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS is 161 + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS); + // CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS is 1229 + request.addParameter(CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS, new int[] {CMQCFC.MQIACF_ALL}); + try { + // Note that agent.send() method is synchronized + logger.debug( + "sending PCF agent request to query queuemanager {}", context.getQueueManagerName()); + long startTime = System.currentTimeMillis(); + List responses = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queuemanager metrics query response for {} received in {} milliseconds", + context.getQueueManagerName(), + endTime); + if (responses.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + Attributes attributes = Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()); + if (context.getMetricsConfig().isIbmMqManagerStatusEnabled()) { + int status = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_Q_MGR_STATUS); + statusGauge.set(status, attributes); + } + if (context.getMetricsConfig().isIbmMqConnectionCountEnabled()) { + int count = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_CONNECTION_COUNT); + connectionCountGauge.set(count, attributes); + } + if (context.getMetricsConfig().isIbmMqRestartLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_RESTART_LOG_SIZE); + restartLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqReusableLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_REUSABLE_LOG_SIZE); + reuseLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqArchiveLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_ARCHIVE_LOG_SIZE); + archiveLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqManagerMaxActiveChannelsEnabled()) { + int maxActiveChannels = context.getQueueManager().getMaxActiveChannels(); + maxActiveChannelsGauge.set(maxActiveChannels, attributes); + } + } catch (Exception e) { + logger.error(e.getMessage()); + throw new IllegalStateException(e); + } finally { + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for queuemanager is {} milliseconds", exitTime); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java new file mode 100644 index 000000000..35370fb73 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class QueueMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueMetricsCollector.class); + + private final List> publishers = new ArrayList<>(); + private final InquireQCmdCollector inquireQueueCmd; + private final ExecutorService threadPool; + private final ConfigWrapper config; + + public QueueMetricsCollector(Meter meter, ExecutorService threadPool, ConfigWrapper config) { + this.threadPool = threadPool; + this.config = config; + QueueCollectionBuddy queueBuddy = + new QueueCollectionBuddy(meter, new QueueCollectorSharedState()); + this.inquireQueueCmd = new InquireQCmdCollector(queueBuddy); + publishers.add(new InquireQStatusCmdCollector(queueBuddy)); + publishers.add(new ResetQStatsCmdCollector(queueBuddy)); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting queue metrics..."); + + // first collect all queue types. + inquireQueueCmd.accept(context); + + // schedule all other jobs in parallel. + List> taskJobs = new ArrayList<>(); + for (Consumer p : publishers) { + taskJobs.add( + () -> { + p.accept(context); + return null; + }); + } + + try { + int timeout = this.config.getInt("queueMetricsCollectionTimeoutInSeconds", 20); + threadPool.invokeAll(taskJobs, timeout, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.error("The thread was interrupted ", e); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java new file mode 100644 index 000000000..f7e56134d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java @@ -0,0 +1,142 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ReadConfigurationEventQueueCollector + implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(ReadConfigurationEventQueueCollector.class); + private final long bootTime; + private final LongGauge maxHandlesGauge; + + public ReadConfigurationEventQueueCollector(Meter meter) { + this.bootTime = System.currentTimeMillis(); + this.maxHandlesGauge = Metrics.createIbmMqManagerMaxHandles(meter); + } + + @Nullable + private PCFMessage findLastUpdate( + MetricsCollectorContext context, long entryTime, String configurationQueueName) + throws Exception { + // find the last update: + PCFMessage candidate = null; + + boolean consumeEvents = + context.getQueueManager().getConsumeConfigurationEventInterval() > 0 + && (entryTime - this.bootTime) + % context.getQueueManager().getConsumeConfigurationEventInterval() + == 0; + + MQQueue queue = null; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + if (!consumeEvents) { + // we are not consuming the events. + queueAccessOptions |= MQConstants.MQOO_BROWSE; + } + queue = context.getMqQueueManager().accessQueue(configurationQueueName, queueAccessOptions); + int maxSequenceNumber = 0; + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + if (!consumeEvents) { + getOptions.options |= MQConstants.MQGMO_BROWSE_NEXT; + } + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage received = new PCFMessage(message); + if (received.getMsgSeqNumber() > maxSequenceNumber) { + maxSequenceNumber = received.getMsgSeqNumber(); + candidate = received; + } + + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + return candidate; + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String configurationQueueName = context.getQueueManager().getConfigurationQueueName(); + logger.info( + "sending PCF agent request to read configuration events from queue {}", + configurationQueueName); + try { + + PCFMessage candidate = findLastUpdate(context, entryTime, configurationQueueName); + + if (candidate == null) { + if (context.getQueueManager().isRefreshQueueManagerConfigurationEnabled()) { + // no event found. + // we issue a refresh request, which will generate a configuration event on the + // configuration event queue. + // note this may incur a performance cost to the queue manager. + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_REFRESH_Q_MGR); + request.addParameter(CMQCFC.MQIACF_REFRESH_TYPE, CMQCFC.MQRT_CONFIGURATION); + request.addParameter(CMQCFC.MQIACF_OBJECT_TYPE, CMQC.MQOT_Q_MGR); + context.send(request); + // try again: + candidate = findLastUpdate(context, entryTime, configurationQueueName); + } + } + + if (candidate != null) { + if (context.getMetricsConfig().isIbmMqManagerMaxHandlesEnabled()) { + int maxHandles = candidate.getIntParameterValue(CMQC.MQIA_MAX_HANDLES); + maxHandlesGauge.set( + maxHandles, Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManager().getName())); + } + } + + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting configuration events for queue " + + configurationQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for configuration events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java new file mode 100644 index 000000000..6f75acfe2 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class ResetQStatsCmdCollector implements Consumer { + + static final int[] ATTRIBUTES = + new int[] {CMQC.MQIA_HIGH_Q_DEPTH, CMQC.MQIA_MSG_DEQ_COUNT, CMQC.MQIA_MSG_ENQ_COUNT}; + + private static final Logger logger = LoggerFactory.getLogger(ResetQStatsCmdCollector.class); + + static final String COMMAND = "MQCMD_RESET_Q_STATS"; + private final QueueCollectionBuddy queueBuddy; + + ResetQStatsCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command {}", COMMAND); + long entryTime = System.currentTimeMillis(); + + logger.debug( + "Attributes being sent along PCF agent request to query queue metrics: {} for command {}", + Arrays.toString(ATTRIBUTES), + COMMAND); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_RESET_Q_STATS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088310_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_RESET_Q_STATS); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command {}", + exitTime, + COMMAND); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java new file mode 100644 index 000000000..76ff2a957 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class TopicMetricsCollector implements Consumer { + private static final Logger logger = LoggerFactory.getLogger(TopicMetricsCollector.class); + private final InquireTStatusCmdCollector inquireTStatusCmdCollector; + + public TopicMetricsCollector(Meter meter) { + this.inquireTStatusCmdCollector = new InquireTStatusCmdCollector(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting Topic metrics..."); + inquireTStatusCmdCollector.accept(context); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java new file mode 100644 index 000000000..68ede61c7 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Utilities reading configuration and create domain objects */ +final class Config { + + private static final Logger logger = LoggerFactory.getLogger(Config.class); + + private Config() {} + + static void setUpSslConnection(Map config) { + getConfigValueAndSetSystemProperty(config, "keyStorePath", "javax.net.ssl.keyStore"); + getConfigValueAndSetSystemProperty( + config, "keyStorePassword", "javax.net.ssl.keyStorePassword"); + getConfigValueAndSetSystemProperty(config, "trustStorePath", "javax.net.ssl.trustStorePath"); + getConfigValueAndSetSystemProperty( + config, "trustStorePassword", "javax.net.ssl.trustStorePassword"); + } + + private static void getConfigValueAndSetSystemProperty( + Map otlpConfig, String configKey, String systemKey) { + Object configValue = otlpConfig.get(configKey); + if (configValue instanceof String && !((String) configValue).trim().isEmpty()) { + System.setProperty(systemKey, (String) configValue); + } + } + + static void configureSecurity(ConfigWrapper config) { + Map sslConnection = config.getSslConnection(); + if (sslConnection.isEmpty()) { + logger.debug( + "ssl truststore and keystore are not configured in config.yml, if SSL is enabled, pass them as jvm args"); + return; + } + + configureTrustStore(sslConnection); + configureKeyStore(sslConnection); + } + + private static void configureTrustStore(Map sslConnection) { + String trustStorePath = sslConnection.get("trustStorePath"); + if (trustStorePath == null || trustStorePath.isEmpty()) { + logger.debug( + "trustStorePath is not set in config.yml, ignoring setting trustStorePath as system property"); + return; + } + + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + logger.debug("System property set for javax.net.ssl.trustStore is {}", trustStorePath); + + String trustStorePassword = sslConnection.get("trustStorePassword"); + + if (trustStorePassword != null && !trustStorePassword.isEmpty()) { + System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); + logger.debug("System property set for javax.net.ssl.trustStorePassword is xxxxx"); + } + } + + private static void configureKeyStore(Map sslConnection) { + String keyStorePath = sslConnection.get("keyStorePath"); + if (keyStorePath == null || keyStorePath.isEmpty()) { + logger.debug( + "keyStorePath is not set in config.yml, ignoring setting keyStorePath as system property"); + return; + } + + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + logger.debug("System property set for javax.net.ssl.keyStore is {}", keyStorePath); + String keyStorePassword = sslConnection.get("keyStorePassword"); + if (keyStorePassword != null && !keyStorePassword.isEmpty()) { + System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword); + logger.debug("System property set for javax.net.ssl.keyStorePassword is xxxxx"); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java new file mode 100644 index 000000000..5f1d71e65 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java @@ -0,0 +1,129 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static java.util.Collections.emptyList; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +/** Low-fi domain-specific yaml wrapper. */ +public final class ConfigWrapper { + + private static final Logger logger = LoggerFactory.getLogger(ConfigWrapper.class); + + private static final int DEFAULT_THREADS = 20; + private static final int DEFAULT_DELAY_SECONDS = 60; + private static final int DEFAULT_INITIAL_DELAY = 0; + + private final Map config; + + private ConfigWrapper(Map config) { + this.config = config; + } + + public static ConfigWrapper parse(String configFile) throws IOException { + Yaml yaml = new Yaml(); + Map config = + yaml.load(Files.newBufferedReader(Paths.get(configFile), Charset.defaultCharset())); + return new ConfigWrapper(config); + } + + public int getNumberOfThreads() { + int value = defaultedInt(getTaskSchedule(), "numberOfThreads", DEFAULT_THREADS); + if (value < DEFAULT_THREADS) { + logger.warn( + "numberOfThreads {} is less than the minimum number of threads allowed. Using {} instead.", + value, + DEFAULT_THREADS); + value = DEFAULT_THREADS; + } + return value; + } + + int getTaskDelaySeconds() { + return defaultedInt(getTaskSchedule(), "taskDelaySeconds", DEFAULT_DELAY_SECONDS); + } + + Duration getTaskDelay() { + return Duration.ofSeconds(getTaskDelaySeconds()); + } + + int getTaskInitialDelaySeconds() { + return defaultedInt(getTaskSchedule(), "initialDelaySeconds", DEFAULT_INITIAL_DELAY); + } + + @NotNull + @SuppressWarnings("unchecked") + List getQueueManagerNames() { + return getQueueManagers().stream() + .map(o -> (Map) o) + .map(x -> x.get("name")) + .collect(Collectors.toList()); + } + + @NotNull + @SuppressWarnings("unchecked") + public List> getQueueManagers() { + List> result = (List>) config.get("queueManagers"); + if (result == null) { + return emptyList(); + } + return result; + } + + @NotNull + @SuppressWarnings("unchecked") + public Map getSslConnection() { + Map result = (Map) config.get("sslConnection"); + if (result == null) { + return Collections.emptyMap(); + } + return result; + } + + public int getInt(String key, int defaultValue) { + Object result = config.get(key); + if (result == null) { + return defaultValue; + } + return (Integer) result; + } + + @NotNull + @SuppressWarnings("unchecked") + public Map getMetrics() { + Object metrics = config.get("metrics"); + if (!(metrics instanceof Map)) { + throw new IllegalArgumentException("config metrics section is missing"); + } + return (Map) metrics; + } + + private static int defaultedInt(Map section, String key, int defaultValue) { + Object val = section.get(key); + return val instanceof Integer ? (Integer) val : defaultValue; + } + + @SuppressWarnings("unchecked") + private Map getTaskSchedule() { + if (config.get("taskSchedule") instanceof Map) { + return (Map) config.get("taskSchedule"); + } + return Collections.emptyMap(); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java new file mode 100644 index 000000000..abd7e69fb --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.ibm.mq.WmqMonitor; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("SystemOut") +public final class Main { + + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + private Main() {} + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("Usage: Main "); + System.exit(1); + } + + try { + Class.forName("com.ibm.mq.headers.MQDataException"); + } catch (ClassNotFoundException e) { + System.err.println("IBM MQ jar is missing from classpath."); + System.exit(1); + } + + String configFile = args[0]; + + ConfigWrapper config = ConfigWrapper.parse(configFile); + + Thread.UncaughtExceptionHandler handler = + (t, e) -> logger.error("Unhandled exception in thread pool", e); + logger.debug("Initializing thread pool with {} threads", config.getNumberOfThreads()); + ScheduledExecutorService service = + Executors.newScheduledThreadPool( + config.getNumberOfThreads(), + r -> { + Thread thread = new Thread(r); + thread.setUncaughtExceptionHandler(handler); + return thread; + }); + + Config.configureSecurity(config); + Config.setUpSslConnection(config.getSslConnection()); + + run(config, service); + } + + public static void run(ConfigWrapper config, ScheduledExecutorService service) { + + AutoConfiguredOpenTelemetrySdk sdk = + AutoConfiguredOpenTelemetrySdk.builder() + .addMeterProviderCustomizer( + (builder, configProps) -> builder.setResource(Resource.empty())) + .build(); + + OpenTelemetrySdk otel = sdk.getOpenTelemetrySdk(); + + run(config, service, otel); + } + + @VisibleForTesting + public static void run( + ConfigWrapper config, ScheduledExecutorService service, OpenTelemetry otel) { + WmqMonitor monitor = new WmqMonitor(config, service, otel.getMeter("websphere/mq")); + ScheduledFuture unused = + service.scheduleAtFixedRate( + monitor::run, + config.getTaskInitialDelaySeconds(), + config.getTaskDelaySeconds(), + TimeUnit.SECONDS); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java new file mode 100644 index 000000000..cefa9e5d9 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.util; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.WmqContext; +import io.opentelemetry.ibm.mq.config.QueueManager; +import java.util.Hashtable; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WmqUtil { + + private static final Logger logger = LoggerFactory.getLogger(WmqUtil.class); + + private WmqUtil() {} + + public static PCFMessageAgent initPcfMessageAgent( + QueueManager queueManager, MQQueueManager ibmQueueManager) { + try { + PCFMessageAgent agent; + if (isNotNullOrEmpty(queueManager.getModelQueueName()) + && isNotNullOrEmpty(queueManager.getReplyQueuePrefix())) { + logger.debug("Initializing the PCF agent for model queue and reply queue prefix."); + agent = new PCFMessageAgent(); + agent.setModelQueueName(queueManager.getModelQueueName()); + agent.setReplyQueuePrefix(queueManager.getReplyQueuePrefix()); + logger.debug("Connecting to queueManager to set the modelQueueName and replyQueuePrefix."); + agent.connect(ibmQueueManager); + } else { + agent = new PCFMessageAgent(ibmQueueManager); + } + if (queueManager.getCcsid() != Integer.MIN_VALUE) { + agent.setCharacterSet(queueManager.getCcsid()); + } + + if (queueManager.getEncoding() != Integer.MIN_VALUE) { + agent.setEncoding(queueManager.getEncoding()); + } + logger.debug( + "Initialized PCFMessageAgent for queueManager {} in thread {}", + agent.getQManagerName(), + Thread.currentThread().getName()); + return agent; + } catch (MQDataException mqe) { + logger.error(mqe.getMessage(), mqe); + throw new IllegalStateException(mqe); + } + } + + @SuppressWarnings("rawtypes") + public static MQQueueManager connectToQueueManager(QueueManager queueManager) { + WmqContext auth = new WmqContext(queueManager); + Hashtable env = auth.getMqEnvironment(); + try { + MQQueueManager ibmQueueManager = new MQQueueManager(queueManager.getName(), env); + logger.debug( + "MQQueueManager connection initiated for queueManager {} in thread {}", + queueManager.getName(), + Thread.currentThread().getName()); + return ibmQueueManager; + } catch (MQException mqe) { + logger.error(mqe.getMessage(), mqe); + throw new IllegalStateException(mqe.getMessage()); + } + } + + private static boolean isNotNullOrEmpty(@Nullable String str) { + return str != null && !str.isEmpty(); + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java new file mode 100644 index 000000000..b4ae65e88 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java @@ -0,0 +1,227 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQRC_SELECTOR_ERROR; +import static com.ibm.mq.constants.CMQCFC.MQRCCF_CHL_STATUS_NOT_FOUND; +import static io.opentelemetry.ibm.mq.metricscollector.MetricAssert.assertThatMetric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +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.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChannelMetricsCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + ChannelMetricsCollector classUnderTest; + QueueManager queueManager; + MetricsCollectorContext context; + Meter meter; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + void testPublishMetrics() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireChannelStatusCmd()); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + + List metricsList = + new ArrayList<>( + Arrays.asList( + "ibm.mq.message.count", + "ibm.mq.status", + "ibm.mq.byte.sent", + "ibm.mq.byte.received", + "ibm.mq.buffers.sent", + "ibm.mq.buffers.received")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.message.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(17); + } + + if (metric.getName().equals("ibm.mq.status")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(3); + } + if (metric.getName().equals("ibm.mq.byte.sent")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(6984); + } + if (metric.getName().equals("ibm.mq.byte.received")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(5772); + } + if (metric.getName().equals("ibm.mq.buffers.sent")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(19); + } + if (metric.getName().equals("ibm.mq.buffers.received")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(20); + } + } + } + assertThat(metricsList).isEmpty(); + } + + /* + Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 42 (MQCMD_INQUIRE_CHANNEL_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 3] + MQCFST [type: 4, strucLength: 24, parameter: 3501 (MQCACH_FIRST/MQCACH_CHANNEL_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIN [type: 3, strucLength: 16, parameter: 1523 (MQIACH_CHANNEL_INSTANCE_TYPE), value: 1011] + MQCFIL [type: 5, strucLength: 48, parameter: 1524 (MQIACH_CHANNEL_INSTANCE_ATTRS), count: 8, values: {3501, 3506, 1527, 1534, 1538, 1535, 1539, 1536}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 42 (MQCMD_INQUIRE_CHANNEL_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 11] + MQCFST [type: 4, strucLength: 40, parameter: 3501 (MQCACH_FIRST/MQCACH_CHANNEL_NAME), codedCharSetId: 819, stringLength: 20, string: DEV.ADMIN.SVRCONN ] + MQCFIN [type: 3, strucLength: 16, parameter: 1511 (MQIACH_CHANNEL_TYPE), value: 7] + MQCFIN [type: 3, strucLength: 16, parameter: 1539 (MQIACH_BUFFERS_RCVD/MQIACH_BUFFERS_RECEIVED), value: 20] + MQCFIN [type: 3, strucLength: 16, parameter: 1538 (MQIACH_BUFFERS_SENT), value: 19] + MQCFIN [type: 3, strucLength: 16, parameter: 1536 (MQIACH_BYTES_RCVD/MQIACH_BYTES_RECEIVED), value: 5772] + MQCFIN [type: 3, strucLength: 16, parameter: 1535 (MQIACH_BYTES_SENT), value: 6984] + MQCFST [type: 4, strucLength: 284, parameter: 3506 (MQCACH_CONNECTION_NAME), codedCharSetId: 819, stringLength: 264, string: 172.17.0.1] + MQCFIN [type: 3, strucLength: 16, parameter: 1523 (MQIACH_CHANNEL_INSTANCE_TYPE), value: 1011] + MQCFIN [type: 3, strucLength: 16, parameter: 1534 (MQIACH_MSGS), value: 17] + MQCFIN [type: 3, strucLength: 16, parameter: 1527 (MQIACH_CHANNEL_STATUS), value: 3] + MQCFIN [type: 3, strucLength: 16, parameter: 1609 (MQIACH_CHANNEL_SUBSTATE), value: 300] + */ + + private static PCFMessage[] createPCFResponseForInquireChannelStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 1, true); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "DEV.ADMIN.SVRCONN"); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response1.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response1.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response1.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response1.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response1.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.1 "); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response1.addParameter(CMQCFC.MQIACH_MSGS, 17); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-03"); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.44"); + response1.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000C"); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 2, true); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "DEV.APP.SVRCONN"); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response2.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response2.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response2.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response2.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response2.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.2 "); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response2.addParameter(CMQCFC.MQIACH_MSGS, 17); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-04"); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.45"); + response2.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000D"); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 2, true); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "TEST.APP.SVRCONN"); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response3.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response3.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response3.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response3.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response3.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.2 "); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response3.addParameter(CMQCFC.MQIACH_MSGS, 17); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-05"); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.46"); + response3.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000E"); + + return new PCFMessage[] {response1, response2, response3}; + } + + @Test + void testPublishMetrics_nullResponse() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenReturn(null); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + assertThat(otelTesting.getMetrics()).isEmpty(); + } + + @Test + void testPublishMetrics_emptyResponse() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenReturn(new PCFMessage[] {}); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + assertThat(otelTesting.getMetrics()).isEmpty(); + } + + @ParameterizedTest + @MethodSource("exceptionsToThrow") + void testPublishMetrics_pfException(Exception exceptionToThrow) throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenThrow(exceptionToThrow); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + + List exported = otelTesting.getMetrics(); + assertThat(exported.get(0).getLongGaugeData().getPoints()).hasSize(1); + assertThatMetric(exported.get(0), 0).hasName("ibm.mq.manager.active.channels").hasValue(0); + } + + static Stream exceptionsToThrow() { + return Stream.of( + arguments(new RuntimeException("KBAOOM")), + arguments(new PCFException(91, MQRCCF_CHL_STATUS_NOT_FOUND, "flimflam")), + arguments(new PCFException(4, MQRC_SELECTOR_ERROR, "shazbot")), + arguments(new PCFException(4, 42, "boz"))); + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java new file mode 100644 index 000000000..a0e06e336 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.CMQXC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class InquireChannelCmdCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + InquireChannelCmdCollector classUnderTest; + + MetricsCollectorContext context; + Meter meter; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + QueueManager queueManager = + mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireChannelCmd()); + classUnderTest = new InquireChannelCmdCollector(meter); + classUnderTest.accept(context); + List metricsList = + new ArrayList<>( + Arrays.asList( + "ibm.mq.message.retry.count", + "ibm.mq.message.received.count", + "ibm.mq.message.sent.count")); + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.message.retry.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(22); + } + if (metric.getName().equals("ibm.mq.message.received.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(42); + } + if (metric.getName().equals("ibm.mq.message.sent.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(64); + } + } + } + assertThat(metricsList).isEmpty(); + } + + private static PCFMessage[] createPCFResponseForInquireChannelCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL, 1, true); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "my.channel"); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, CMQXC.MQCHT_SVRCONN); + response1.addParameter(CMQCFC.MQIACH_MR_COUNT, 22); + response1.addParameter(CMQCFC.MQIACH_MSGS_RECEIVED, 42); + response1.addParameter(CMQCFC.MQIACH_MSGS_SENT, 64); + response1.addParameter(CMQCFC.MQIACH_MAX_INSTANCES, 3); + response1.addParameter(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT, 3); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java new file mode 100644 index 000000000..6170e31f4 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ListenerMetricsCollectorTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + ListenerMetricsCollector classUnderTest; + QueueManager queueManager; + ConfigWrapper config; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + } + + @Test + public void testPublishMetrics() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireListenerStatusCmd()); + + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + classUnderTest = + new ListenerMetricsCollector( + otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + classUnderTest.accept(context); + + MetricData metric = otelTesting.getMetrics().get(0); + assertThat(metric.getName()).isEqualTo("ibm.mq.listener.status"); + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + + /* + Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 98 (MQCMD_INQUIRE_LISTENER_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 24, parameter: 3554 (MQCACH_LISTENER_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIL [type: 5, strucLength: 24, parameter: 1223 (MQIACF_LISTENER_STATUS_ATTRS), count: 2, values: {3554, 1599}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 98 (MQCMD_INQUIRE_LISTENER_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 48, parameter: 3554 (MQCACH_LISTENER_NAME), codedCharSetId: 819, stringLength: 27, string: SYSTEM.DEFAULT.LISTENER.TCP] + MQCFIN [type: 3, strucLength: 16, parameter: 1599 (MQIACH_LISTENER_STATUS), value: 2] + */ + + private static PCFMessage[] createPCFResponseForInquireListenerStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 1, true); + response1.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "DEV.DEFAULT.LISTENER.TCP"); + response1.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 2); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 2, true); + response2.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "DEV.LISTENER.TCP"); + response2.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 3); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 3, true); + response3.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "SYSTEM.LISTENER.TCP"); + response3.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 1); + + return new PCFMessage[] {response1, response2, response3}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java new file mode 100644 index 000000000..25d173d3d --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import org.assertj.core.api.Assertions; + +public class MetricAssert { + + private final MetricData metric; + private final int pointOffset; + + public MetricAssert(MetricData metric, int pointOffset) { + this.metric = metric; + this.pointOffset = pointOffset; + } + + static MetricAssert assertThatMetric(MetricData metric, int pointOffset) { + return new MetricAssert(metric, pointOffset); + } + + MetricAssert hasName(String name) { + Assertions.assertThat(metric.getName()).isEqualTo(name); + return this; + } + + MetricAssert hasValue(long value) { + Assertions.assertThat( + ((LongPointData) metric.getLongGaugeData().getPoints().toArray()[this.pointOffset]) + .getValue()) + .isEqualTo(value); + return this; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java new file mode 100644 index 000000000..bd0c1ef97 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java @@ -0,0 +1,339 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class QueueCollectionBuddyTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + QueueCollectionBuddy classUnderTest; + QueueManager queueManager; + MetricsCollectorContext collectorContext; + Meter meter; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + collectorContext = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + QueueCollectorSharedState sharedState = new QueueCollectorSharedState(); + sharedState.putQueueType("AMQ.5AF1608820C7D76E", "local-transmission"); + sharedState.putQueueType("DEV.DEAD.LETTER.QUEUE", "local-transmission"); + sharedState.putQueueType("DEV.QUEUE.1", "local-transmission"); + PCFMessage request = createPCFRequestForInquireQStatusCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForInquireQStatusCmd()); + + classUnderTest = new QueueCollectionBuddy(meter, sharedState); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", InquireQStatusCmdCollector.ATTRIBUTES); + + Map> expectedValues = + new HashMap<>( + ImmutableMap.of( + "DEV.DEAD.LETTER.QUEUE", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.oldest.msg.age", -1L, + "ibm.mq.uncommitted.messages", 0L, + "ibm.mq.onqtime.short_period", -1L, + "ibm.mq.onqtime.long_period", -1L, + "ibm.mq.queue.depth", 0L)), + "DEV.QUEUE.1", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.oldest.msg.age", -1L, + "ibm.mq.uncommitted.messages", 10L, + "ibm.mq.onqtime.short_period", -1L, + "ibm.mq.onqtime.long_period", -1L, + "ibm.mq.queue.depth", 1L)))); + + for (MetricData metric : otelTesting.getMetrics()) { + for (LongPointData d : metric.getLongGaugeData().getPoints()) { + String queueName = d.getAttributes().get(MESSAGING_DESTINATION_NAME); + Long expectedValue = expectedValues.get(queueName).remove(metric.getName()); + assertThat(d.getValue()).isEqualTo(expectedValue); + } + } + + for (Map metrics : expectedValues.values()) { + assertThat(metrics).isEmpty(); + } + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForInquireQCmd() throws Exception { + PCFMessage request = createPCFRequestForInquireQCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForInquireQCmd()); + classUnderTest = new QueueCollectionBuddy(meter, new QueueCollectorSharedState()); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", InquireQCmdCollector.ATTRIBUTES); + + Map> expectedValues = + new HashMap<>( + ImmutableMap.of( + "DEV.DEAD.LETTER.QUEUE", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.queue.depth", 2L, + "ibm.mq.max.queue.depth", 5000L, + "ibm.mq.open.input.count", 2L, + "ibm.mq.open.output.count", 2L)), + "DEV.QUEUE.1", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.queue.depth", 3L, + "ibm.mq.max.queue.depth", 5000L, + "ibm.mq.open.input.count", 3L, + "ibm.mq.open.output.count", 3L)))); + + for (MetricData metric : otelTesting.getMetrics()) { + for (LongPointData d : metric.getLongGaugeData().getPoints()) { + String queueName = d.getAttributes().get(MESSAGING_DESTINATION_NAME); + Long expectedValue = expectedValues.get(queueName).remove(metric.getName()); + assertThat(d.getValue()).isEqualTo(expectedValue); + } + } + + for (Map metrics : expectedValues.values()) { + assertThat(metrics).isEmpty(); + } + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForResetQStatsCmd() throws Exception { + QueueCollectorSharedState sharedState = new QueueCollectorSharedState(); + sharedState.putQueueType("AMQ.5AF1608820C7D76E", "local-transmission"); + sharedState.putQueueType("DEV.DEAD.LETTER.QUEUE", "local-transmission"); + sharedState.putQueueType("DEV.QUEUE.1", "local-transmission"); + PCFMessage request = createPCFRequestForResetQStatsCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForResetQStatsCmd()); + classUnderTest = new QueueCollectionBuddy(meter, sharedState); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", ResetQStatsCmdCollector.ATTRIBUTES); + + for (MetricData metric : otelTesting.getMetrics()) { + Iterator iterator = metric.getLongGaugeData().getPoints().iterator(); + if (metric.getName().equals("ibm.mq.high.queue.depth")) { + assertThat(iterator.next().getValue()).isEqualTo(10); + } else if (metric.getName().equals("ibm.mq.message.deq.count")) { + assertThat(iterator.next().getValue()).isEqualTo(0); + } else if (metric.getName().equals("ibm.mq.message.enq.count")) { + assertThat(iterator.next().getValue()).isEqualTo(3); + } + } + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIL [type: 5, strucLength: 32, parameter: 1026 (MQIACF_Q_STATUS_ATTRS), count: 4, values: {2016, 1226, 1227, 1027}] + */ + private static PCFMessage createPCFRequestForInquireQStatusCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_STATUS); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + request.addParameter(CMQCFC.MQIACF_Q_STATUS_ATTRS, new int[] {2016, 1226, 1227, 1027}); + return request; + } + + /* + 0 = {PCFMessage@6026} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: AMQ.5AF1608820C7D76E ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 12] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + + 1 = {PCFMessage@6029} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 2, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + + 2 = {PCFMessage@6030} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 3, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.QUEUE.1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + */ + private static PCFMessage[] createPCFResponseForInquireQStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "AMQ.5AF1608820C7D76E"); + response1.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response1.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 12); + response1.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response1.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response1.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 0); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 2, false); + response2.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response2.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response2.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 0); + response2.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response2.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response2.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 0); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 1, false); + response3.addParameter(CMQC.MQCA_Q_NAME, "DEV.QUEUE.1"); + response3.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response3.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 1); + response3.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response3.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response3.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 10); + + return new PCFMessage[] {response1, response2, response3}; + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 3] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1001] + MQCFIL [type: 5, strucLength: 36, parameter: 1002 (MQIACF_Q_ATTRS), count: 5, values: {2016, 15, 3, 17, 18}] + */ + private static PCFMessage createPCFRequestForInquireQCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_ALL); + request.addParameter(CMQCFC.MQIACF_Q_ATTRS, new int[] {2016, 15, 3, 17, 18}); + return request; + } + + /* + 0 = {PCFMessage@6059} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: AMQ.5AF1608820C76D80 ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 1]" + + 1 = {PCFMessage@6060} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 2, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 0]" + + 2 = {PCFMessage@6061} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 3, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.QUEUE.1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 0]" + */ + + private static PCFMessage[] createPCFResponseForInquireQCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "AMQ.5AF1608820C76D80"); + response1.addParameter(CMQC.MQIA_Q_TYPE, 1); + response1.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 1); + response1.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 1); + response1.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response1.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 1); + response1.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_NORMAL); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 2, false); + response2.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response2.addParameter(CMQC.MQIA_Q_TYPE, 1); + response2.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 2); + response2.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 2); + response2.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response2.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 2); + response2.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_TRANSMISSION); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 3, false); + response3.addParameter(CMQC.MQCA_Q_NAME, "DEV.QUEUE.1"); + response3.addParameter(CMQC.MQIA_Q_TYPE, 1); + response3.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 3); + response3.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 3); + response3.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response3.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 3); + response3.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_TRANSMISSION); + + return new PCFMessage[] {response1, response2, response3}; + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 17 (MQCMD_RESET_Q_STATS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 1] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + */ + private static PCFMessage createPCFRequestForResetQStatsCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_RESET_Q_STATS); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + return request; + } + + /* + 0 = {PCFMessage@6144} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 17 (MQCMD_RESET_Q_STATS), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 5] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 37 (MQIA_MSG_ENQ_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 38 (MQIA_MSG_DEQ_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 36 (MQIA_HIGH_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 35 (MQIA_TIME_SINCE_RESET), value: 65]" + */ + private static PCFMessage[] createPCFResponseForResetQStatsCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_RESET_Q_STATS, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response1.addParameter(CMQC.MQIA_MSG_ENQ_COUNT, 3); + response1.addParameter(CMQC.MQIA_MSG_DEQ_COUNT, 0); + response1.addParameter(CMQC.MQIA_HIGH_Q_DEPTH, 10); + response1.addParameter(CMQC.MQIA_TIME_SINCE_RESET, 65); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java new file mode 100644 index 000000000..dde400b02 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class QueueManagerMetricsCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + QueueManagerMetricsCollector classUnderTest; + QueueManager queueManager; + MetricsCollectorContext context; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireQMgrStatusCmd()); + classUnderTest = + new QueueManagerMetricsCollector( + otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + classUnderTest.accept(context); + List metricsList = new ArrayList<>(singletonList("ibm.mq.manager.status")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()).isEqualTo(2); + } + } + assertThat(metricsList).isEmpty(); + } + + /* Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 161 (MQCMD_INQUIRE_Q_MGR_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 1] + MQCFIL [type: 5, strucLength: 20, parameter: 1229 (MQIACF_Q_MGR_STATUS_ATTRS), count: 1, values: {1009}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 161 (MQCMD_INQUIRE_Q_MGR_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 23] + MQCFST [type: 4, strucLength: 68, parameter: 2015 (MQCA_Q_MGR_NAME), codedCharSetId: 819, stringLength: 48, string: QM1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 1149 (MQIACF_Q_MGR_STATUS), value: 2] + MQCFST [type: 4, strucLength: 20, parameter: 3208 (null), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1416 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1232 (MQIACF_CHINIT_STATUS), value: 2] + MQCFIN [type: 3, strucLength: 16, parameter: 1233 (MQIACF_CMD_SERVER_STATUS), value: 2] + MQCFIN [type: 3, strucLength: 16, parameter: 1230 (MQIACF_CONNECTION_COUNT), value: 23] + MQCFST [type: 4, strucLength: 20, parameter: 3071 (MQCACF_CURRENT_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFST [type: 4, strucLength: 20, parameter: 2115 (null), codedCharSetId: 819, stringLength: 0, string: ] + MQCFST [type: 4, strucLength: 36, parameter: 2116 (null), codedCharSetId: 819, stringLength: 13, string: Installation1] + MQCFST [type: 4, strucLength: 28, parameter: 2117 (null), codedCharSetId: 819, stringLength: 8, string: /opt/mqm] + MQCFIN [type: 3, strucLength: 16, parameter: 1409 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1420 (null), value: 9] + MQCFST [type: 4, strucLength: 44, parameter: 3074 (MQCACF_LOG_PATH), codedCharSetId: 819, stringLength: 24, string: /var/mqm/log/QM1/active/] + MQCFIN [type: 3, strucLength: 16, parameter: 1421 (null), value: 9] + MQCFST [type: 4, strucLength: 20, parameter: 3073 (MQCACF_MEDIA_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1417 (null), value: 0] + MQCFST [type: 4, strucLength: 20, parameter: 3072 (MQCACF_RESTART_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1418 (null), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 1419 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1325 (null), value: 0] + MQCFST [type: 4, strucLength: 32, parameter: 3175 (null), codedCharSetId: 819, stringLength: 12, string: 2018-05-08 ] + MQCFST [type: 4, strucLength: 28, parameter: 3176 (null), codedCharSetId: 819, stringLength: 8, string: 08.32.08] + */ + + private static PCFMessage[] createPCFResponseForInquireQMgrStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS, 1, true); + response1.addParameter(CMQC.MQCA_Q_MGR_NAME, "QM1"); + response1.addParameter(CMQCFC.MQIACF_Q_MGR_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CHINIT_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CMD_SERVER_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CONNECTION_COUNT, 23); + response1.addParameter(CMQCFC.MQCACF_CURRENT_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQCACF_LOG_PATH, "/var/mqm/log/QM1/active/"); + response1.addParameter(CMQCFC.MQCACF_MEDIA_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQCACF_RESTART_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQIACF_RESTART_LOG_SIZE, 42); + response1.addParameter(CMQCFC.MQIACF_REUSABLE_LOG_SIZE, 42); + response1.addParameter(CMQCFC.MQIACF_ARCHIVE_LOG_SIZE, 42); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java new file mode 100644 index 000000000..865501573 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java @@ -0,0 +1,113 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TopicMetricsCollectorTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + TopicMetricsCollector classUnderTest; + QueueManager queueManager; + ConfigWrapper config; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + } + + @Test + void testPublishMetrics() throws Exception { + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + classUnderTest = + new TopicMetricsCollector(otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireTopicStatusCmd()); + + classUnderTest.accept(context); + + List metricsList = + new ArrayList<>(Arrays.asList("ibm.mq.publish.count", "ibm.mq.subscription.count")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.publish.count")) { + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + if (metric.getName().equals("ibm.mq.subscription.count")) { + Set values = new HashSet<>(); + values.add(3L); + values.add(4L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + } + } + assertThat(metricsList).isEmpty(); + } + + private static PCFMessage[] createPCFResponseForInquireTopicStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 1, false); + response1.addParameter(CMQC.MQCA_TOPIC_STRING, "test"); + response1.addParameter(CMQC.MQIA_PUB_COUNT, 2); + response1.addParameter(CMQC.MQIA_SUB_COUNT, 3); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 2, false); + response2.addParameter(CMQC.MQCA_TOPIC_STRING, "dev"); + response2.addParameter(CMQC.MQIA_PUB_COUNT, 3); + response2.addParameter(CMQC.MQIA_SUB_COUNT, 4); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 3, false); + response3.addParameter(CMQC.MQCA_TOPIC_STRING, "system"); + response3.addParameter(CMQC.MQIA_PUB_COUNT, 5); + response3.addParameter(CMQC.MQIA_SUB_COUNT, 6); + + return new PCFMessage[] {response1, response2, response3}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java new file mode 100644 index 000000000..73aadc6a1 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConfigTest { + + private Properties systemProperties; + + @BeforeEach + public void cacheSystemProperties() { + systemProperties = new Properties(); + for (Map.Entry entry : System.getProperties().entrySet()) { + systemProperties.put(entry.getKey().toString(), entry.getValue().toString()); + } + } + + @Test + void testSSLConnection() { + Config.setUpSslConnection( + new HashMap( + ImmutableMap.of( + "keyStorePath", "foo", + "trustStorePath", "bar", + "keyStorePassword", "password", + "trustStorePassword", "password1"))); + + assertThat(System.getProperties().get("javax.net.ssl.keyStore")).isEqualTo("foo"); + assertThat(System.getProperties().get("javax.net.ssl.trustStorePath")).isEqualTo("bar"); + assertThat(System.getProperties().get("javax.net.ssl.keyStorePassword")).isEqualTo("password"); + assertThat(System.getProperties().get("javax.net.ssl.trustStorePassword")) + .isEqualTo("password1"); + } + + @AfterEach + public void resetSystemProperties() { + System.getProperties().clear(); + for (Map.Entry entry : systemProperties.entrySet()) { + System.setProperty(entry.getKey().toString(), entry.getValue().toString()); + } + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java new file mode 100644 index 000000000..3d2397cc0 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConfigWrapperTest { + + String file; + + @BeforeEach + void setUp() { + file = ConfigWrapperTest.class.getResource("/conf/config.yml").getFile(); + // Windows resources can contain a colon, which can't be mapped to a Path cleanly + // They look like /D:/a/path/to/whatever + file = file.replaceFirst("^/([A-Z]:)/", "$1/"); + } + + @Test + void testQueueManagerNames() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getQueueManagerNames()).isEqualTo(singletonList("QM1")); + } + + @Test + void testNumberOfThreads() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getNumberOfThreads()).isEqualTo(20); + } + + @Test + void testTaskDelay() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getTaskDelay()).isEqualTo(Duration.of(27, ChronoUnit.SECONDS)); + } + + @Test + void testTaskInitialDelay() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getTaskInitialDelaySeconds()).isEqualTo(0); + } +} diff --git a/ibm-mq-metrics/src/test/resources/conf/config.yml b/ibm-mq-metrics/src/test/resources/conf/config.yml new file mode 100644 index 000000000..51a11d53f --- /dev/null +++ b/ibm-mq-metrics/src/test/resources/conf/config.yml @@ -0,0 +1,217 @@ +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #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: "app" + password: "passw0rd" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + # Name of the temporary dynamic queue holding the configuration events. This queue contains information regarding the configuration of the queue manager, notable MaxChannels and MaxActiveChannels. + # If unset, the default queue name `SYSTEM.ADMIN.CONFIG.EVENT` is applied. + # Configuration events need to be enabled explicitly in the queue manager configuration. See https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=monitoring-configuration-events for reference. + #configurationQueueName: "SYSTEM.ADMIN.CONFIG.EVENT" + + # Interval in milliseconds at which the configuration events in the configuration queue can be consumed. + # By default, no events are consumed. + #consumeConfigurationEventInterval: 600000 # 10 minutes + + # Enable running a queue manager refresh request to reload its configuration and create a configuration event. + # This action is only executed if no configuration events are found when reading the configuration queue.name: + # By default, this action is disabled. + #refreshQueueManagerConfigurationEnabled: false + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "" + + + 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", "TEST"] + + 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: "EQUALS" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["system","$SYS"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + +#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: 27 + + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: https://localhost:4318 + otel.exporter.otlp.protocol: http/protobuf + otel.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 b/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 new file mode 100644 index 000000000..2fcbdd64b --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import io.opentelemetry.api.common.AttributeKey; + +// This file is generated using weaver. Do not edit manually. + +/** Attribute definitions generated from a Weaver model. Do not edit manually. */ +public final class IbmMqAttributes { +{% for attr in ctx %} + /** + {{ attr.brief }} */{% if attr.type == 'string' %} + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = stringKey("{{attr.name}}"); + {% elif attr.type == 'int' %} + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = longKey("{{attr.name}}"); + {% else %} + // UNHANDLED TYPE PLEASE FIXME + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = ??key("{{attr.name}}"); + {% endif %} +{% endfor %} + private IbmMqAttributes(){} +} diff --git a/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 b/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 new file mode 100644 index 000000000..1b0097826 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Function; + +// This file is generated using weaver. Do not edit manually. + +/** Metric definitions generated from a Weaver model. Do not edit manually. */ +public final class Metrics { +public final static Function MIBY_TO_BYTES = x -> x * 1024L * 1024L; +private Metrics(){ +} +{% for metric in ctx %} + + {% if metric.instrument == "gauge" %} + public static LongGauge create{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}(Meter meter) { + return meter + .gaugeBuilder("{{ metric.metric_name }}") + .ofLongs() + .setUnit("{{ metric.unit }}") + .setDescription("{{ metric.brief }}") + .build(); + } + {% elif metric.instrument == "counter" %} + public static LongCounter create{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}(Meter meter) { + return meter + .counterBuilder("{{ metric.metric_name }}") + .setUnit("{{ metric.unit }}") + .setDescription("{{ metric.brief }}") + .build(); + } + {% endif %} +{% endfor %} +} diff --git a/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 b/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 new file mode 100644 index 000000000..df6fae562 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.Map; + +// This file is generated using weaver. Do not edit manually. + +/** Configuration of metrics as defined in config.yml. */ +public final class MetricsConfig { + + private final Map config; + + public MetricsConfig(ConfigWrapper config) { + this.config = config.getMetrics(); + } +{% for metric in ctx %} + public boolean is{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}Enabled() { + return isEnabled("{{ metric.metric_name }}"); + } +{% endfor %} + private boolean isEnabled(String key) { + Object metricInfo = config.get(key); + if (!(metricInfo instanceof Map)) { + return false; + } + Object enabled = ((Map) metricInfo).get("enabled"); + if (enabled instanceof Boolean) { + return (Boolean) enabled; + } + return false; + } +} diff --git a/ibm-mq-metrics/templates/registry/java/weaver.yaml b/ibm-mq-metrics/templates/registry/java/weaver.yaml new file mode 100644 index 000000000..ece71c233 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/weaver.yaml @@ -0,0 +1,10 @@ +templates: + - template: Metrics.java.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single + - template: MetricsConfig.java.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single + - template: IbmMqAttributes.java.j2 + filter: '.groups | map(select(.type == "attribute_group")) | map(.attributes[])' + application_mode: single diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 new file mode 100644 index 000000000..9c0fea34e --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 @@ -0,0 +1,36 @@ +{% import 'examples_macros.j2' as examples %} +{% macro type(attribute) %}{%- if attribute.type is mapping %} +{%- if attribute.type.members[0].value is string %}string{%- endif %} +{%- if attribute.type.members[0].value is int %}int{%- endif %} +{%- if attribute.type.members[0].value is float %}double{%- endif %} +{%- elif attribute.type == "template[boolean]" %}boolean +{%- elif attribute.type == "template[int]" %}int +{%- elif attribute.type == "template[double]" %}double +{%- elif attribute.type == "template[string]" %}string +{%- elif attribute.type == "template[boolean[]]" %}boolean[] +{%- elif attribute.type == "template[int[]]" %}int[] +{%- elif attribute.type == "template[double[]]" %}double[] +{%- elif attribute.type == "template[string[]]" %}string[] +{%- else %}{{ attribute.type | trim }}{%- endif %}{% endmacro %} + +{% macro name(attribute) %}{%- if attribute.type is startingwith("template[") %}`{{ attribute.name }}.` +{%- else %}`{{ attribute.name }}`{%- endif %}{% endmacro %} + +{% macro find_lineage(attr_id, lineage) %}{% if attr_id in lineage %}{{lineage[attr_id].source_group}}{% endif %}{% endmacro %} + +{% macro name_with_link(attribute, attribute_registry_base_url, lineage_attributes) %}[{{name(attribute)}}]({{attribute_registry_base_url}}/{{ find_lineage(attribute.name, lineage_attributes) | split_id | list | reject("eq", "registry")| first | kebab_case }}.md){% endmacro %} + +{% macro display_name(group) %} +{%- if 'display_name' in group %}{{ group.display_name }} +{%- else %}{{ group.id | split_id | list | reject("eq", "registry") | join(" ") | title_case | acronym }} Attributes +{%- endif %}{% endmacro %} + +{% macro heading_link_fragments(title) %}{{ title | trim | lower | replace(" ", "-") | replace("(", "") | replace(")", "") | replace("/", "") | replace("\\", "") | replace(".", "") | replace("!", "") | replace("?", "") | replace("~", "") | replace("#", "")}}{% endmacro %} + +{% macro humanize(text) %} + {{- text.replace('_', ' ') -}} +{% endmacro %} + +{% macro sentence_case(text) %} + {{- text[:1].upper() + text[1:].lower() -}} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 new file mode 100644 index 000000000..e55c288b3 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 @@ -0,0 +1,51 @@ +{#- This template is rendered per top-level registry namespace. -#} +{#- It consists of two variables: -#} +{#- - id: The top-level namespace id. -#} +{#- - groups: A sequence of all attribute groups under this namespace. -#} +{#- This includes deprecated groups. -#} +{%- import 'stability.j2' as stability -%} +{%- import 'notes.j2' as notes -%} +{%- import 'enum_macros.j2' as enums -%} +{%- import 'attribute_macros.j2' as attrs -%} +{%- import 'examples_macros.j2' as examples -%} +{%- set my_file_name = ctx.id | lower | kebab_case ~ ".md" -%} +{{- template.set_file_name(my_file_name) -}} +{%- set groups = namespace(deprecated=[], non_deprecated=[]) -%} +{%- for group in ctx.groups | sort(attribute="id") -%} +{%- if group.id[-10:] == "deprecated" -%} +{%- set groups.deprecated = groups.deprecated + [group] -%} +{%- else -%} +{%- set groups.non_deprecated = groups.non_deprecated + [group] -%} +{%- endif -%} +{%- endfor -%} +{%- set attr_groups = groups.non_deprecated + groups.deprecated -%} + + + + +# {{ attrs.humanize(attrs.sentence_case(ctx.id)) | acronym }} + +{%- if attr_groups | length > 1 %} +{% for group in attr_groups %} +- [{{ attrs.display_name(group) }}](#{{ attrs.heading_link_fragments(attrs.display_name(group)) }}) +{%- endfor -%} +{%- endif %} +{% for group in attr_groups %} +## {{ attrs.display_name(group) }} + +{% if group.brief.endswith("\n") -%} +{{ group.brief }} +{% else -%} +{{ group.brief }} +{{"\n"}} +{%- endif -%} +| Attribute | Type | Description | Examples | Stability | +|---|---|---|---|---| +{%- for attribute in group.attributes | sort(attribute="name") %}{% set attr_anchor = attribute.name | kebab_case %} +| {{ attrs.name(attribute) }} | {{ attrs.type(attribute) }} | {{ attribute.brief | trim }}{{ notes.add({"note": attribute.note, "name": attribute.name}) }} | {{ examples.format(attribute) | trim }} | {{ stability.badge(attribute.stability, attribute.deprecated) | trim }} | +{%- endfor %} +{{ notes.render() }} +{%- for enum in group.attributes | sort(attribute="name") %} +{%- if enum.type is mapping -%}{{ enums.table(enum, notes) }}{% endif %} +{%- endfor -%} +{%- endfor -%} diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 new file mode 100644 index 000000000..b40c54788 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 @@ -0,0 +1,13 @@ +{% import 'requirement.j2' as requirement %} +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'attribute_macros.j2' as attrs %} +{% import 'enum_macros.j2' as enums %} +{% import 'sampling_macros.j2' as sampling %} +{% import 'examples_macros.j2' as examples %} +{#- Macro for creating attribute table -#} +{% macro generate(attributes, tag_filter, attribute_registry_base_url, lineage_attributes) %}{% if (tag_filter | length == 0) %}{% set filtered_attributes = attributes %}{% else %}{% set filtered_attributes = attributes | selectattr("tag", "in", tag_filter) %}{% endif %}{% if filtered_attributes | length > 0 %}| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +{% for attribute in filtered_attributes | attribute_sort %}| {{ attrs.name(attribute) }} | {{ attrs.type(attribute) }} | {{ attribute.brief | trim }}{{ notes.add({"note": attribute.note, "name": attribute.name}) }} | {{ examples.format(attribute) | trim }} | {{ requirement.render({"level": attribute.requirement_level, "name": attribute.name}, notes) | trim }} | {{ stability.badge(attribute.stability, attribute.deprecated) | trim }} | +{% endfor %}{{ notes.render() }}{{ sampling.snippet(filtered_attributes, attribute_registry_base_url, lineage_attributes) }}{{ enums.tables(filtered_attributes | selectattr("type", "mapping"), notes) }} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 b/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 new file mode 100644 index 000000000..b600f6dd7 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 @@ -0,0 +1,18 @@ +{% import 'requirement.j2' as requirement %} +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'enum_macros.j2' as enums %} +{% import 'examples_macros.j2' as examples %} +{% macro flatten(fields, ns, depth) %}{% if fields %}{% for f in fields | sort(attribute="id") %} +{% set ns.flat = [ns.flat, [{'field':f,'depth':depth}]] | flatten %}{% if f.fields %}{% set _= flatten(f.fields, ns, depth + 1) %}{% endif %} +{% endfor %}{% endif %}{% endmacro %} +{% macro field_name(field, depth) -%} +{%- set name= " " * 2 * depth ~ '`' ~ field.id ~ '`' -%} +{%- if (field.type == "map") or (field.type == "map[]") %}{{ name ~ ":"}}{% else -%} +{{ name }}{% endif %}{% endmacro %} +{#- Macro for creating body table -#} +{% macro generate(fields) %}{% if (fields | length > 0) %}{% set ns = namespace(flat=[])%}{% set _ = flatten(fields, ns, 0) %}| Body Field | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +{% for f in ns.flat %}| {{ field_name(f.field, f.depth) }} | {{ f.field.type }} | {{ f.field.brief | trim }}{{ notes.add({"note": f.field.note}) }} | {{ examples.format(f.field) | trim }} | {{ requirement.render({"level": f.field.requirement_level, "name": f.field.id}, notes) | trim }} | {{ stability.badge(f.field.stability, f.field.deprecated) | trim }} | +{% endfor %}{{ notes.render() }}{{ enums.field_tables(ns.flat | map(attribute="field") | selectattr("type", "eq", "enum"), notes) -}} +{%- endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 new file mode 100644 index 000000000..d20327e14 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 @@ -0,0 +1,30 @@ +{% import 'stability.j2' as stability %} +{% macro filter(member) %}{% if (member.deprecated is none or member.deprecated == "") %}{{ "True" }}{% else %}{{ "False" }}{% endif %}{% endmacro %} +{% macro table(enum, notes) %} +--- + +`{{enum.name}}` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used. + +| Value | Description | Stability | +|---|---|---| +{% for espec in enum.type.members | sort(attribute='value') %} +{%- if filter(espec) == "True" -%} +| `{{ espec.value }}` | {{ (espec.brief or espec.id) | trim }}{{ notes.add({"note": espec.note}) }} | {{ stability.badge(espec.stability, espec.deprecated) }} | +{% endif %}{% endfor %}{{ notes.render() }}{% endmacro %} +{% macro tables(enums, notes) -%} +{% for enum in enums | sort(attribute="name") -%} +{{ table(enum, notes) -}} +{% endfor %}{% endmacro %} +{% macro field_table(enum, notes) %} +`{{enum.id}}` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used. + +| Value | Description | Stability | +|---|---|---| +{% for espec in enum.members | sort(attribute='value') %} +{%- if filter(espec) == "True" -%} +| `{{ espec.value }}` | {{ (espec.brief or espec.id) | trim }}{{ notes.add({"note": espec.note}) }} | {{ stability.badge(espec.stability, espec.deprecated) }} | +{% endif %}{% endfor %}{{ notes.render() }}{% endmacro %} +{% macro field_tables(enums, notes) -%} +{% for enum in enums | sort(attribute="id") -%} +{{ field_table(enum, notes) -}} +{% endfor %}{% endmacro %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 new file mode 100644 index 000000000..c6ef8ef6f --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 @@ -0,0 +1,16 @@ +{#- Macros for simplifying creating "Event" documentation. -#} +{% import 'stability.j2' as stability %} +{% import 'body_field_table.j2' as body_table %} +{% macro header(event) %}**Status:** {{ stability.badge(event.stability, event.deprecated) }} + +The event name MUST be `{{ event.name }}`. + +{{ event.brief | trim }} +{%if event.note %} +{{ event.note | trim }} +{% endif %} +{% endmacro %} +{% macro body(body) %}{% if body %}**Body fields:** + +{{ body_table.generate(body.fields) }} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 new file mode 100644 index 000000000..bbd840d67 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 @@ -0,0 +1,17 @@ +{% macro print_examples(examples) %}{%- for e in examples %}{%if loop.first == false %}; {% endif %}`{{ e | trim }}`{%- endfor %}{% endmacro %} + +{% macro format(item) %}{%- if item.examples %} +{%- if "[]" in item.type and "template" not in item.type %} +{%- if item.examples is sequence %} +{%- if item.examples | select("sequence") | length == 0 %}`{{ item.examples | trim }}` +{%- else %}{{ print_examples(item.examples) }} +{%- endif %} +{%- else %}`[{{ item.examples | trim }}]` +{%- endif %} +{%- elif item.examples is sequence %}{{ print_examples(item.examples) }} +{%- else %}`{{ item.examples | trim }}` +{%- endif %}{%- elif item.type is mapping %} +{%- for e in item.type.members %}{% if loop.index0 < 3 %}{% if loop.first == false %}; {% endif %}`{{ e.value | trim }}`{% endif %}{%- endfor %} +{%- elif item.type == "enum" -%} +{%- for e in item.members %}{% if loop.index0 < 3 %}{% if loop.first == false %}; {% endif %}`{{ e.value | trim }}`{% endif %}{%- endfor %} +{%- endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 new file mode 100644 index 000000000..deae008dd --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 @@ -0,0 +1,8 @@ +{% macro instrument(type) -%} +{%- if type == "gauge" %}Gauge +{% elif type == "counter" %}Counter +{% elif type == "updowncounter" %}UpDownCounter +{% elif type == "histogram" %}Histogram +{% else %}{{ type }} +{%- endif %} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 b/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 new file mode 100644 index 000000000..ddbd191c6 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 @@ -0,0 +1,7 @@ +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'metric_macros.j2' as metrics %} +{% macro generate(group) %}| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `{{ group.metric_name }}` | {{ metrics.instrument(group.instrument) | trim }} | `{{ group.unit }}` | {{ group.brief | trim }}{{ notes.add({"note": group.note}) }} | {{ stability.badge(group.stability, group.deprecated) | trim }} | +{{ notes.render() }}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 b/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 new file mode 100644 index 000000000..0ff8153bc --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 @@ -0,0 +1,14 @@ +{%- import 'attribute_table.j2' as at -%} +{%- import 'metric_table.j2' as mt -%} + +# Produced Metrics + +{% for metric in ctx %} +## Metric `{{metric.metric_name}}` + +{{ mt.generate(metric) }} + +### `{{metric.metric_name}}` Attributes + +{{ at.generate(metric.attributes, "", "", metric.lineage.attributes) }} +{% endfor %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/notes.j2 b/ibm-mq-metrics/templates/registry/markdown/notes.j2 new file mode 100644 index 000000000..a96b4270e --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/notes.j2 @@ -0,0 +1,9 @@ +{%- set ns = namespace(notes=[],index=0) -%} +{%- macro add(note) %}{% if note.note %}{% set ns.notes = [ns.notes, [note]] | flatten %} [{{ ns.notes | length + ns.index }}]{% endif %}{% endmacro %} +{%- macro add_with_limit(note) %}{% if note.note | length > 50 %}{% set ns.notes = [ns.notes, [note]] | flatten %} [{{ ns.notes | length + ns.index }}]{% elif note.note %} {{ note.note | trim }}{% endif %}{% endmacro %} +{% macro render() %}{% if ns.notes | length > 0 %} +{%- for note in ns.notes %} +{% if note.name %}**[{{ns.index+loop.index}}] `{{note.name}}`:** {{ note.note | trim }}{% else -%}**[{{ns.index+loop.index}}]:** {{ note.note | trim }} {%- endif -%} +{%- if not loop.last -%}{{"\n"}}{%- endif -%} +{% endfor %}{% set ns.index = ns.notes | length + ns.index %}{% set ns.notes = [] %} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 b/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 new file mode 100644 index 000000000..8aea78a9f --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 @@ -0,0 +1,42 @@ +{{- template.set_file_name("README.md") -}} + + + + + +# Attribute registry + +The attributes registry is the place where attributes are defined. An attribute definition covers the following properties of an attribute: + +- the `id` (the fully qualified name) of the attribute +- the `type` of the attribute +- the `stability` of the attribute +- a `brief` description of the attribute and optionally a longer `note` +- example values + +Attributes defined in the registry can be used in different semantic conventions. Attributes should be included in this registry before they are used in semantic conventions. Semantic conventions may override all the properties of an attribute except for the `id`, `type` and `stability` in case it's required for a particular context. In addition, semantic conventions specify the requirement level of an attribute in the corresponding context. + +A definition of an attribute in the registry doesn't necessarily imply that the attribute is used in any of the semantic conventions. + +If applicable, application developers are encouraged to use existing attributes from this registry. See also [these recommendations][developers recommendations] regarding attribute selection and attribute naming for custom use cases. + +All registered attributes are listed by namespace in this registry. + +> [!WARNING] +> +> The following registry overview is a work in progress. +> +> Further attribute namespaces are currently being migrated and will appear in this registry soon. + +Currently, the following namespaces exist: + +{% for bundle in ctx %} +{%- set my_file_name = bundle.id | kebab_case ~ ".md" -%} +- [{{ bundle.id | title_case | acronym }}]({{ my_file_name }}) +{% endfor %} +[developers recommendations]: ../general/naming.md#recommendations-for-application-developers + diff --git a/ibm-mq-metrics/templates/registry/markdown/requirement.j2 b/ibm-mq-metrics/templates/registry/markdown/requirement.j2 new file mode 100644 index 000000000..1f5714de3 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/requirement.j2 @@ -0,0 +1,9 @@ +{% macro render(attr, notes) -%} +{%- if attr.level == "recommended" %}`Recommended` +{% elif attr.level == "required" %}`Required` +{% elif attr.level == "opt_in" %}`Opt-In` +{% elif attr.level.conditionally_required %}`Conditionally Required`{{ notes.add_with_limit({"note": attr.level.conditionally_required, "name": attr.name}) }} +{% elif attr.level.recommended %}`Recommended`{{ notes.add_with_limit({"note": attr.level.recommended, "name": attr.name}) }} +{% else %}{{ level }} +{%- endif %} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 new file mode 100644 index 000000000..f19680cbd --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 @@ -0,0 +1,16 @@ +{#- Macros for simplifying creating "Resource" documentation. -#} +{% import 'stability.j2' as stability %} +{% macro real_stability(resource) %} +{% if resource.attributes | map(attribute='stability') | unique | length > 1 -%} +{{ stability.badge("mixed", "") }} +{%- else -%} +{{ stability.badge(resource.stability, resource.deprecated) }} +{%- endif %} +{% endmacro %} +{% macro header(resource) %} +**Status:** {{ real_stability(resource) | trim }} + +**type:** `{{ resource.name }}` + +**Description:** {{ resource.brief }} +{% endmacro %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 new file mode 100644 index 000000000..07929e7d4 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 @@ -0,0 +1,7 @@ +{% import 'attribute_macros.j2' as attrs %} +{% macro snippet(attributes, attribute_registry_base_url, lineage_attributes) %}{% set sampling_attributes = attributes | selectattr("sampling_relevant", "true") %}{% if sampling_attributes | length > 0 %} +The following attributes can be important for making sampling decisions +and SHOULD be provided **at span creation time** (if provided at all): + +{% for attribute in sampling_attributes | sort(attribute="name") %}* {{ attrs.name_with_link(attribute, attribute_registry_base_url, lineage_attributes) }} +{% endfor %}{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 b/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 new file mode 100644 index 000000000..6b225c811 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 @@ -0,0 +1,32 @@ + + + + + +{%- import 'attribute_table.j2' as at -%} +{%- import 'metric_table.j2' as mt -%} +{%- import 'event_macros.j2' as event -%} +{%- import 'resource_macros.j2' as resource %} + +{% macro generate_event(group) -%} +{{ event.header(group) }}{{ generate_attributes(group) }}{{ event.body(group.body) }}{% endmacro -%} +{%- macro generate_resource(group) -%} +{{ resource.header(group) }}{{ generate_attributes(group) }}{% endmacro -%} +{%- macro generate_metric(group) -%} +{{ mt.generate(group) }} +{{ generate_attributes(group) }}{% endmacro -%} +{%- macro generate_attributes(group) -%} +{{ at.generate(group.attributes, tag_filter, attribute_registry_base_url, group.lineage.attributes) }}{% endmacro -%} + +{% if group.type == "event" -%} +{{ generate_event(group) -}} +{%- elif group.type == "resource" -%} +{{ generate_resource(group) }} +{%- elif group.type == "metric" -%} +{{ generate_metric(group) }} +{%- else -%} +{{ generate_attributes(group) -}} +{% endif -%} + + + diff --git a/ibm-mq-metrics/templates/registry/markdown/stability.j2 b/ibm-mq-metrics/templates/registry/markdown/stability.j2 new file mode 100644 index 000000000..cc9ed7bde --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/stability.j2 @@ -0,0 +1,11 @@ +{% macro badge(stability, deprecated) -%} +{%- if deprecated %}![Deprecated](https://img.shields.io/badge/-deprecated-red)
{{ deprecated.note | trim }} +{%- elif stability == "mixed" %}![Mixed](https://img.shields.io/badge/-mixed-yellow) +{%- elif stability == "stable" %}![Stable](https://img.shields.io/badge/-stable-lightgreen) +{%- elif stability == "release_candidate" %}![Release Candidate](https://img.shields.io/badge/-rc-mediumorchid) +{%- elif stability == "deprecated" %}![Deprecated](https://img.shields.io/badge/-deprecated-red) +{%- elif stability == "experimental" %}![Development](https://img.shields.io/badge/-development-blue) +{%- elif stability == "development" %}![Development](https://img.shields.io/badge/-development-blue) +{%- else %}{{ "Unknown stability." }} +{%- endif %} +{%- endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/weaver.yaml b/ibm-mq-metrics/templates/registry/markdown/weaver.yaml new file mode 100644 index 000000000..6a1dd65ad --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: metrics.md.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single \ No newline at end of file diff --git a/ibm-mq-metrics/weaver.Dockerfile b/ibm-mq-metrics/weaver.Dockerfile new file mode 100644 index 000000000..b1698505d --- /dev/null +++ b/ibm-mq-metrics/weaver.Dockerfile @@ -0,0 +1,6 @@ +# DO NOT BUILD +# This file is just for tracking dependencies of the semantic convention build. +# Dependabot can keep this file up to date with latest containers. + +# Weaver is used to generate markdown docs, and enforce policies on the model and run integration tests. +FROM otel/weaver:v0.15.1 AS weaver \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f83b17cd2..00f27fdc0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ include(":consistent-sampling") include(":dependencyManagement") include(":disk-buffering") include(":example") +include(":ibm-mq-metrics") include(":jfr-events") include(":jfr-connection") include(":jmx-metrics")