javaactivemq-classic

Access ActiveMQ Classic JMS client statistics


I have an application that uses org.apache.activemq:activemq-client:6.1.3 to talk to an ActiveMQ Classic broker. I want to expose producer and consumer metrics (e.g. the number of bytes sent and received) collected by the client to Micrometer, and from there on to Grafana.

I can find various statistics by browsing through objects in the debugger, but I cannot find a way to query those statistics via the JMS 2.0 implementation classes (e.g. ActiveMQProducer which implements JMSProducer). For example, using reflection, this works:

 public static void instrumentProducer(JMSProducer producer) {
    if (!(producer instanceof ActiveMQProducer amqProducer)) {
      throw new IllegalArgumentException("Expected an instance of ActiveMQProducer, but got: " + producer.getClass());
    }

    /* Is there a way to do this without reflection? */
    var field = Arrays.stream(ActiveMQProducer.class.getDeclaredFields())
            .filter(f -> ActiveMQMessageProducer.class.isAssignableFrom(f.getType()))
            .findFirst()
                    .orElseThrow(() -> new NoSuchElementException("Could not find ActiveMQMessageProducer field to scrape metrics from"));

    try {
      field.trySetAccessible();
      var messageProducer = (ActiveMQMessageProducer) field.get(amqProducer);
      messageProducer.getStats().setEnabled(true);
      var messageCount = messageProducer.getProducerStats().getMessageCount();

      // Ideally this should be a counter, but I'll figure that out.
      Gauge.builder("activemq.producer.message.count", messageCount::getCount)
          .baseUnit(messageCount.getUnit())
          .description(messageCount.getDescription())
          .register(globalRegistry);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to instrument JMSProducer", e);
    }
  }

But this is complex and could break if the implementation changes.

Is there a way to access these statistics without using reflection, or is there a library out there that can do it for me so my team doesn't have to maintain this brittle, non-obvious code?

Our application is built in Quarkus (we're intentionally not using smallrye-messaging) in Java 17.


Solution

  • The current ActiveMQProducer provides no way to access the underlying ActiveMQMessageProducer without reflection, and I'm not aware of any library that encapsulates such reflection in an easy-to-consume API. You can either continue using reflection or open a Jira and request a way to access the underlying ActiveMQMessageProducer from ActiveMQProducer.

    Another option would be to create your own wrapper so you can track the stats yourself. This would allow you to use any JMS client implementation rather than just one (e.g. the OpenWire JMS client from ActiveMQ Classic). It's possible you could even get such a feature added to a connection pool implementation like this one (which is relatively widely used). This would ease your maintenance burden assuming you're already using a pool.

    It's also worth noting here that the OpenWire JMS client from ActiveMQ Classic doesn't fully implement JMS 2. See more details here.