Skip to content

feat(prometheus): Add native /metrics endpoint - #2402

Open
hai-ben wants to merge 1 commit into
apache:mainfrom
hai-ben:prometheus-poc
Open

feat(prometheus): Add native /metrics endpoint#2402
hai-ben wants to merge 1 commit into
apache:mainfrom
hai-ben:prometheus-poc

Conversation

@hai-ben

@hai-ben hai-ben commented Aug 5, 2026

Copy link
Copy Markdown

New activemq-prometheus module that adds a /metrics endpoint to the broker that vends metrics in the Prometheus format without requiring any sort of open JMX port. The plugin relies on the existing Jetty auth systems and only scrapes MBean during a request, so it doesn't put any additional load on the broker unless someone is actually using it and users define their own Prometheus scrapers and monitoring setup.

It depends only on the jakarta stuff jetty already does.

I've included an example grafana dashboard in this PR just as a way to get up an running quickly (./activemq-prometheus/src/main/resources/example-grafana-dashboard.json). This wouldn't be in the final PR, instead something like it would be uploaded to grafana.com/dashboards.

See Discussion: #2226

Questions for the community:

  • What permissions should be required to reach this endpoint? admins? everyone?
  • What do you think about the chosen metrics and names?
  • Because it's no load without use, what are your thoughts about having it on by default?

@graben

graben commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Maybe an idea for improvement for broker with many queues an topics. Collect metrics periodically in the background and answer http request with the last collected result. This avoids a lot of preasure on the broker and keep getting results back to prometheus "quickly" available. In my experience is better to not not the most actual data but have at most actual data by providing broker stability.

@hai-ben

hai-ben commented Aug 5, 2026

Copy link
Copy Markdown
Author

I like the idea @graben. I know for RabbitMQ, there's an internal metrics store that both their console and their prometheus plugin scrape from, so the plugin still has 0 load on idle, does that pattern fix what you're getting at?

A couple of other parallel additions I might also just add in this vein:

  • A simple cache with some configurable TTL, additional callers inside the TTL would represent only marginal load
  • Adding support to stream the response back to callers

@hai-ben

hai-ben commented Aug 5, 2026

Copy link
Copy Markdown
Author

Looking into the source a little more, it looks like everything is already just describing MBeans for metrics anyways. The reason RabbitMQ has that pattern is that those metrics need to be actually aggregated, ActiveMQ MBeans are already ready to use. So I'm not sure I see the savings that pre-caching metrics has over lazily updating a cache in response to a caller.

I'll still add the streaming response and the lazy TTL cache, as those are definitely worthwhile (imo).

@sergio-d-lemos sergio-d-lemos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! I left some simple comments, I understand this is WIP.


@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
boolean perObject = request != null && "true".equalsIgnoreCase(request.getParameter("per_object"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to use final as much as possible


try {
writeMetrics(ManagementFactory.getPlatformMBeanServer(), writer, perObject);
} catch (Exception exception) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should log this exception too, not just silently drop. So we can know when and what failed

ObjectName pattern = new ObjectName("org.apache.activemq:type=Broker,brokerName=*");
Set<ObjectName> brokers = mBeanServer.queryNames(pattern, null);

for (MetricDefinition metric : BROKER_METRICS) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a curiosity, is there any practical difference between iterating the brokers inside the metrics loop or doing the other way around ?

for (ObjectName broker : brokers) {  
   for (MetricDefinition metric : BROKER_METRICS) {
   ...
   }
}

Do we need to have the metrics grouped together ?

return ((Number) value).doubleValue();
}
} catch (Exception ignored) {
// Some attributes are not available on every ActiveMQ deployment (eg: bridge metrics)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log a warning?

if (value == null) {
return "unknown";
}
return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, just my curiosity. If I have broker called "# TYPE" (or anything else that is meaningful for the prometheus format), it won't mess things up right?

return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
}

private static final class MetricDefinition {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 final classes, final everything!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants