Skip to content

Commit e6e4101

Browse files
committed
Add a Prometheus /metrics endpoint
1 parent bd2f0a5 commit e6e4101

14 files changed

Lines changed: 1307 additions & 0 deletions

File tree

activemq-prometheus/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<!--
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
-->
19+
20+
# ActiveMQ Prometheus Metrics
21+
22+
## Activation
23+
24+
1. Uncomment the `Prometheus Metrics Web Application` block from `conf/jetty/jetty-webapps.xml`.
25+
2. Restart the broker
26+
27+
The endpoint uses the existing Jetty management listener, TLS configuration,
28+
IP allowlist, and JAAS realm. Its path is restricted to the `admins` role
29+
but can be changed in `conf/jetty/jetty-security.xml`.
30+
31+
## Endpoints
32+
33+
Two endpoints because brokers with many destinations might produce large responses:
34+
- `GET /metrics`: broker-level metrics only.
35+
- `GET /metrics?per_object=true`: per-queue, per-topic, and broker-level metrics
36+
37+
## Metrics
38+
39+
### Broker metrics (`activemq_broker_*`)
40+
41+
| Metric | Type | Description |
42+
|--------|------|-------------|
43+
| `connections_count` | gauge | Current number of connections |
44+
| `connections_total` | counter | Total connections since last start |
45+
| `messages_enqueued_total` | counter | Total messages enqueued since last start |
46+
| `messages_dequeued_total` | counter | Total messages dequeued since last start |
47+
| `consumers_count` | gauge | Current number of consumers |
48+
| `producers_count` | gauge | Current number of producers |
49+
| `message_count` | gauge | Current number of messages across all destinations |
50+
| `memory_percent_usage` | gauge | Percent of memory limit used |
51+
| `memory_limit_bytes` | gauge | Memory limit in bytes |
52+
| `store_percent_usage` | gauge | Percent of store limit used |
53+
| `store_limit_bytes` | gauge | Store limit in bytes |
54+
| `temp_percent_usage` | gauge | Percent of temp limit used |
55+
| `temp_limit_bytes` | gauge | Temp limit in bytes |
56+
| `uptime_milliseconds` | gauge | Broker uptime in milliseconds |
57+
58+
### Destination metrics (`activemq_queue_*` / `activemq_topic_*`)
59+
60+
Returned only when `?per_object=true` is set.
61+
62+
| Metric | Type | Description |
63+
|--------|------|-------------|
64+
| `message_count` | gauge | Number of messages in destination |
65+
| `enqueue_count_total` | counter | Total messages enqueued since last start |
66+
| `dequeue_count_total` | counter | Total messages dequeued since last start |
67+
| `dispatch_count_total` | counter | Total messages dispatched since last start |
68+
| `message_inflight_count` | gauge | Messages dispatched but not acknowledged |
69+
| `expired_count_total` | counter | Total messages expired since last start |
70+
| `consumer_count` | gauge | Number of consumers |
71+
| `producer_count` | gauge | Number of producers |
72+
| `memory_percent_usage` | gauge | Percent of destination memory limit used |
73+
| `memory_limit_bytes` | gauge | Memory limit for destination in bytes |
74+
| `memory_usage_bytes` | gauge | Memory used by destination in bytes |
75+
| `store_message_size_bytes` | gauge | Store message size in bytes |
76+
| `average_enqueue_time_milliseconds` | gauge | Average time (since last start) messages waited before dispatch |
77+
78+
## Prometheus configuration
79+
80+
Example yaml configuration for running a Prometheus scraper on the same machine as the broker
81+
```yaml
82+
scrape_configs:
83+
- job_name: activemq
84+
metrics_path: /metrics
85+
params:
86+
per_object: ['true'] # omit for broker-only
87+
basic_auth:
88+
username: admin
89+
password: admin
90+
static_configs:
91+
- targets: ['localhost:8161']
92+
```

activemq-prometheus/pom.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one or more
4+
contributor license agreements. See the NOTICE file distributed with
5+
this work for additional information regarding copyright ownership.
6+
The ASF licenses this file to You under the Apache License, Version 2.0
7+
(the "License"); you may not use this file except in compliance with
8+
the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
19+
20+
<modelVersion>4.0.0</modelVersion>
21+
22+
<parent>
23+
<groupId>org.apache.activemq</groupId>
24+
<artifactId>activemq-parent</artifactId>
25+
<version>6.4.0-SNAPSHOT</version>
26+
</parent>
27+
28+
<artifactId>activemq-prometheus</artifactId>
29+
<packaging>war</packaging>
30+
<name>ActiveMQ :: Prometheus</name>
31+
<description>ActiveMQ Prometheus metrics endpoint</description>
32+
33+
<dependencies>
34+
35+
<!-- =============================== -->
36+
<!-- Required Dependencies -->
37+
<!-- =============================== -->
38+
<dependency>
39+
<groupId>jakarta.servlet</groupId>
40+
<artifactId>jakarta.servlet-api</artifactId>
41+
<scope>provided</scope>
42+
</dependency>
43+
44+
<!-- =============================== -->
45+
<!-- Testing Dependencies -->
46+
<!-- =============================== -->
47+
<dependency>
48+
<groupId>junit</groupId>
49+
<artifactId>junit</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
54+
<build>
55+
<finalName>metrics</finalName>
56+
</build>
57+
58+
</project>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.activemq.prometheus;
18+
19+
import java.io.IOException;
20+
import java.io.PrintWriter;
21+
import java.io.StringWriter;
22+
import java.lang.management.ManagementFactory;
23+
import java.util.Set;
24+
25+
import jakarta.servlet.http.HttpServlet;
26+
import jakarta.servlet.http.HttpServletRequest;
27+
import jakarta.servlet.http.HttpServletResponse;
28+
import javax.management.MBeanServer;
29+
import javax.management.ObjectName;
30+
31+
public class PrometheusMetricsServlet extends HttpServlet {
32+
33+
private static final long serialVersionUID = 1L;
34+
private static final String CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
35+
36+
// Metrics can be easily extended by adding them here
37+
private static final MetricDefinition[] BROKER_METRICS = {
38+
new MetricDefinition("connections", "Current number of connections", "CurrentConnectionsCount", MetricType.GAUGE),
39+
new MetricDefinition("connections_total", "Total connections since last start", "TotalConnectionsCount", MetricType.COUNTER),
40+
new MetricDefinition("messages_enqueued_total", "Total messages enqueued since last start", "TotalEnqueueCount", MetricType.COUNTER),
41+
new MetricDefinition("messages_dequeued_total", "Total messages dequeued since last start", "TotalDequeueCount", MetricType.COUNTER),
42+
new MetricDefinition("consumers", "Current number of consumers", "TotalConsumerCount", MetricType.GAUGE),
43+
new MetricDefinition("producers", "Current number of producers", "TotalProducerCount", MetricType.GAUGE),
44+
new MetricDefinition("messages", "Current number of messages across all destinations", "TotalMessageCount", MetricType.GAUGE),
45+
new MetricDefinition("memory_percent_usage", "Percent (0-100) of memory limit used", "MemoryPercentUsage", MetricType.GAUGE),
46+
new MetricDefinition("memory_limit_bytes", "Memory limit in bytes", "MemoryLimit", MetricType.GAUGE),
47+
new MetricDefinition("store_percent_usage", "Percent (0-100) of store limit used", "StorePercentUsage", MetricType.GAUGE),
48+
new MetricDefinition("store_limit_bytes", "Store limit in bytes", "StoreLimit", MetricType.GAUGE),
49+
new MetricDefinition("temp_percent_usage", "Percent (0-100) of temp limit used", "TempPercentUsage", MetricType.GAUGE),
50+
new MetricDefinition("temp_limit_bytes", "Temp limit in bytes", "TempLimit", MetricType.GAUGE),
51+
new MetricDefinition("uptime_milliseconds", "Broker uptime in milliseconds", "UptimeMillis", MetricType.GAUGE)
52+
};
53+
54+
private static final MetricDefinition[] DESTINATION_METRICS = {
55+
new MetricDefinition("messages", "Number of messages in this destination", "QueueSize", MetricType.GAUGE),
56+
new MetricDefinition("enqueued_total", "Total messages enqueued to this destination since last start", "EnqueueCount", MetricType.COUNTER),
57+
new MetricDefinition("dequeued_total", "Total messages dequeued from destination since last start", "DequeueCount", MetricType.COUNTER),
58+
new MetricDefinition("dispatched_total", "Total messages dispatched from destination since last start", "DispatchCount", MetricType.COUNTER),
59+
new MetricDefinition("message_inflight_count", "Messages dispatched but not acknowledged", "InFlightCount", MetricType.GAUGE),
60+
new MetricDefinition("expired_total", "Total messages expired since last start", "ExpiredCount", MetricType.COUNTER),
61+
new MetricDefinition("consumers", "Number of consumers", "ConsumerCount", MetricType.GAUGE),
62+
new MetricDefinition("producers", "Number of producers", "ProducerCount", MetricType.GAUGE),
63+
new MetricDefinition("memory_percent_usage", "Percent (0-100) of destination memory limit used", "MemoryPercentUsage", MetricType.GAUGE),
64+
new MetricDefinition("memory_limit_bytes", "Memory limit for this destination in bytes", "MemoryLimit", MetricType.GAUGE),
65+
new MetricDefinition("memory_usage_bytes", "Memory used by this destination in bytes", "MemoryUsageByteCount", MetricType.GAUGE),
66+
new MetricDefinition("store_message_size_bytes", "Store message size in bytes", "StoreMessageSize", MetricType.GAUGE),
67+
new MetricDefinition("average_enqueue_time_milliseconds", "Average time (since last start) messages waited before dispatch", "AverageEnqueueTime", MetricType.GAUGE)
68+
};
69+
70+
@Override
71+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
72+
boolean perObject = request != null && "true".equalsIgnoreCase(request.getParameter("per_object"));
73+
74+
StringWriter output = new StringWriter();
75+
PrintWriter writer = new PrintWriter(output);
76+
77+
try {
78+
writeMetrics(ManagementFactory.getPlatformMBeanServer(), writer, perObject);
79+
} catch (Exception exception) {
80+
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Metrics collection failed");
81+
return;
82+
}
83+
84+
response.setContentType(CONTENT_TYPE);
85+
response.setStatus(HttpServletResponse.SC_OK);
86+
response.getWriter().write(output.toString());
87+
}
88+
89+
void writeMetrics(MBeanServer mBeanServer, PrintWriter writer, boolean perObject) throws Exception {
90+
writeBrokerMetrics(mBeanServer, writer);
91+
// Scraping this by default on brokers with lots of queues or topics might be expensive
92+
if (perObject) {
93+
writeDestinationMetrics(mBeanServer, writer, "Queue");
94+
writeDestinationMetrics(mBeanServer, writer, "Topic");
95+
}
96+
writer.flush();
97+
}
98+
99+
private void writeBrokerMetrics(MBeanServer mBeanServer, PrintWriter writer) throws Exception {
100+
ObjectName pattern = new ObjectName("org.apache.activemq:type=Broker,brokerName=*");
101+
Set<ObjectName> brokers = mBeanServer.queryNames(pattern, null);
102+
103+
for (MetricDefinition metric : BROKER_METRICS) {
104+
String metricName = "activemq_broker_" + metric.name;
105+
writeMetadata(writer, metricName, metric);
106+
for (ObjectName broker : brokers) {
107+
String brokerName = sanitizeLabel((String) mBeanServer.getAttribute(broker, "BrokerName"));
108+
String labels = "broker=\"" + brokerName + "\"";
109+
writeSample(writer, metricName, labels, getNumber(mBeanServer, broker, metric.attribute));
110+
}
111+
}
112+
}
113+
114+
private void writeDestinationMetrics(MBeanServer mBeanServer, PrintWriter writer, String type) throws Exception {
115+
String queryPattern = "org.apache.activemq:type=Broker,brokerName=*,destinationType=" + type + ",destinationName=*";
116+
Set<ObjectName> destinations = mBeanServer.queryNames(new ObjectName(queryPattern), null);
117+
String typeLower = type.toLowerCase();
118+
119+
for (MetricDefinition metric : DESTINATION_METRICS) {
120+
String metricName = "activemq_" + typeLower + "_" + metric.name;
121+
writeMetadata(writer, metricName, metric.withFormattedHelp(typeLower));
122+
for (ObjectName destination : destinations) {
123+
String brokerName = sanitizeLabel(destination.getKeyProperty("brokerName"));
124+
String destinationName = sanitizeLabel(destination.getKeyProperty("destinationName"));
125+
String labels = String.format("broker=\"%s\",destination=\"%s\"", brokerName, destinationName);
126+
writeSample(writer, metricName, labels, getNumber(mBeanServer, destination, metric.attribute));
127+
}
128+
}
129+
}
130+
131+
private double getNumber(MBeanServer mBeanServer, ObjectName name, String attribute) {
132+
try {
133+
Object value = mBeanServer.getAttribute(name, attribute);
134+
if (value instanceof Number) {
135+
return ((Number) value).doubleValue();
136+
}
137+
} catch (Exception ignored) {
138+
// Some attributes are not available on every ActiveMQ deployment (eg: bridge metrics)
139+
}
140+
return 0;
141+
}
142+
143+
private void writeMetadata(PrintWriter writer, String metric, MetricDefinition def) {
144+
writer.println("# HELP " + metric + " " + def.help);
145+
writer.println("# TYPE " + metric + " " + def.type.prometheusName());
146+
}
147+
148+
private void writeSample(PrintWriter writer, String metric, String labels, double value) {
149+
if (value == (long) value) {
150+
writer.println(metric + "{" + labels + "} " + (long) value);
151+
} else {
152+
writer.println(metric + "{" + labels + "} " + value);
153+
}
154+
}
155+
156+
static String sanitizeLabel(String value) {
157+
if (value == null) {
158+
return "unknown";
159+
}
160+
return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
161+
}
162+
163+
private static final class MetricDefinition {
164+
private final String name;
165+
private final String help;
166+
private final String attribute;
167+
private final MetricType type;
168+
169+
private MetricDefinition(String name, String help, String attribute, MetricType type) {
170+
this.name = name;
171+
this.help = help;
172+
this.attribute = attribute;
173+
this.type = type;
174+
}
175+
176+
private MetricDefinition withFormattedHelp(String arg) {
177+
return new MetricDefinition(name, String.format(help, arg), attribute, type);
178+
}
179+
}
180+
181+
enum MetricType {
182+
GAUGE("gauge"),
183+
COUNTER("counter");
184+
185+
private final String prometheusName;
186+
187+
MetricType(String prometheusName) {
188+
this.prometheusName = prometheusName;
189+
}
190+
191+
String prometheusName() {
192+
return prometheusName;
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)