New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ARTEMIS-2308 Support exporting metrics #2681
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach is clean and won't couple the broker with Prometheus API and deps so is +100 for me.
After addressed thread-safeness issues, refacoring of some String
constants and maybe considered the MetricsManager
API proposal (that's up to u, given that's just personal taste!) LGTM
@@ -101,19 +103,19 @@ | |||
@Attribute(desc = "names of all bindings (both queues and diverts) bound to this address") | |||
String[] getBindingNames() throws Exception; | |||
|
|||
@Attribute(desc = "number of messages added to all the queues for this address") | |||
@Attribute(desc = "number of messages currently in all queues bound to this address (includes scheduled, paged, and in-delivery messages)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why no refactor this desc
as you've done for the others? eg ROUTED_MESSAGE_COUNT_DESCRIPTION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't refactor the description here because I'm not actually exported this metric since it will be easy to aggregate all the message counts of all the queues on the address in whatever system the metrics are exported to.
|
||
if (server != null && server.getMetricsManager() != null) { | ||
MetricsManager metricsManager = server.getMetricsManager(); | ||
metricsManager.registerQueueGauge("message.count", addressName, queueName, pendingMetrics, metrics -> Double.valueOf(pendingMetrics.getMessageCount()), QueueControl.MESSAGE_COUNT_DESCRIPTION); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to collect in some static final fields all the metrics IDs eg message.count
to avoid mistakes while registering them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure what you mean. Do you mean declaring something like:
static final String MESSAGE_COUNT = "message.count";
And then using MESSAGE_COUNT
in the method invocation?
|
||
private MeterRegistry meterRegistry; | ||
|
||
private Map<String, List<Meter>> meters = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meters::remove
could be called by different threads eg QueueImpl::unregisterMeters
and
same for meters::add
: probably is better to use a thread-safe map here ie ConcurrentHashMap
.
List<Meter>
instead could be left an ArrayList
, because I see that there isn't a strong concurrency requirements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I'll change that.
return meterRegistry; | ||
} | ||
|
||
public void registerQueueGauge(String metricName, String address, String queue, Object object, ToDoubleFunction f, String description) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use QueuePendingMessageMetrics queueState
instead of Object object
to help us to remember what to use there/why
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't possible because a QueuePendingMessageMetrics
isn't always passed into the method (e.g. for all the queue metrics that aren't tracked by the QueuePendingMessageMetrics
object).
import org.apache.activemq.artemis.api.core.management.ResourceNames; | ||
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; | ||
|
||
public class MetricsManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a huge comment; API change proposal...
private final String brokerName;
private final MeterRegistry meterRegistry;
private final Map<String, List<Meter>> meters = new ConcurrentHashMap<>();
public MetricsManager(String brokerName, ActiveMQMetricsPlugin metricsPlugin) {
this.brokerName = brokerName;
meterRegistry = metricsPlugin.getRegistry();
Metrics.globalRegistry.add(meterRegistry);
new JvmMemoryMetrics().bindTo(meterRegistry);
}
public MeterRegistry getMeterRegistry() {
return meterRegistry;
}
@FunctionalInterface
public interface MetricGaugeBuilder {
void register(String metricName, Object state, ToDoubleFunction f, String description);
}
public void registerQueueGauge(String address, String queue, Consumer<MetricGaugeBuilder> builder) {
final MeterRegistry meterRegistry = this.meterRegistry;
if (meterRegistry == null) {
return;
}
final List<Gauge.Builder> newMeters = new ArrayList<>();
builder.accept((metricName, state, f, description) -> {
Gauge.Builder meter = Gauge.builder("artemis." + metricName, state, f).tag("broker", brokerName).tag("address", address).tag("queue", queue).description(description);
newMeters.add(meter);
});
final String resource = ResourceNames.QUEUE + queue;
this.meters.compute(resource, (s, meters) -> {
//the old meters are ignored on purpose
meters = new ArrayList<>(newMeters.size());
for (Gauge.Builder gauge : newMeters) {
meters.add(gauge.register(meterRegistry));
}
return meters;
});
}
public void registerAddressGauge(String address, Consumer<MetricGaugeBuilder> builder) {
final MeterRegistry meterRegistry = this.meterRegistry;
if (meterRegistry == null) {
return;
}
final List<Gauge.Builder> newMeters = new ArrayList<>();
builder.accept((metricName, state, f, description) -> {
Gauge.Builder meter = Gauge.builder("artemis." + metricName, state, f).tag("broker", brokerName).tag("address", address).description(description);
newMeters.add(meter);
});
final String resource = ResourceNames.ADDRESS + address;
this.meters.compute(resource, (s, meters) -> {
//the old meters are ignored on purpose
meters = new ArrayList<>(newMeters.size());
for (Gauge.Builder gauge : newMeters) {
meters.add(gauge.register(meterRegistry));
}
return meters;
});
}
public void registerBrokerGauge(Consumer<MetricGaugeBuilder> builder) {
final MeterRegistry meterRegistry = this.meterRegistry;
if (meterRegistry == null) {
return;
}
final List<Gauge.Builder> newMeters = new ArrayList<>();
builder.accept((metricName, state, f, description) -> {
Gauge.Builder meter = Gauge.builder("artemis." + metricName, state, f).tag("broker", brokerName).description(description);
newMeters.add(meter);
});
final String resource = ResourceNames.BROKER + "." + brokerName;
this.meters.compute(resource, (s, meters) -> {
//the old meters are ignored on purpose
meters = new ArrayList<>(newMeters.size());
for (Gauge.Builder gauge : newMeters) {
meters.add(gauge.register(meterRegistry));
}
return meters;
});
}
public void remove(String component) {
meters.computeIfPresent(component, (s, meters) -> {
if (meters == null) {
return null;
}
for (Meter meter : meters) {
Meter removed = meterRegistry.remove(meter);
if (ActiveMQServerLogger.LOGGER.isDebugEnabled()) {
ActiveMQServerLogger.LOGGER.debug("Removed meter: " + removed.getId());
}
}
return null;
});
}
That could be used with:
if (server != null && server.getMetricsManager() != null) {
server.getMetricsManager().registerQueueGauge(queueName, addressName, builder -> {
builder.register("message.count", pendingMetrics, metrics -> Double.valueOf(pendingMetrics.getMessageCount()), QueueControl.MESSAGE_COUNT_DESCRIPTION);
builder.register(//...other useful metrics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's just me, but I think the existing implementation is easier to read. :)
Is there a particular benefit to your implementation? I'm assuming it has better performance for some esoteric reason. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe :P It has a better behaviour under load, because it won't call many times put on the concurrent hash map, just once...
docs/user-manual/en/metrics.md
Outdated
Important runtime metrics have been instrumented via the Micrometer API, and | ||
all a user needs to do is implement `org.apache.activemq.artemis.core.server.metrics.ActiveMQMetricsPlugin` | ||
in order to instantiate and configure a `io.micrometer.core.instrument.MeterRegistry` | ||
implementation. Many implementations of `MeterRegistr` are available from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sentance seems to end abruptly i assume you meant to put a link to the micrometer web page with a list of these? also looks to be a small typo in MeterRegistr, assume it should be MeterRegister
Great stuff. +1 on this from me. Nice abstraction from implementations. Added one comment on docs where it seems something ends mid-sentance. |
@@ -1012,6 +1012,33 @@ | |||
</xsd:complexType> | |||
</xsd:element> | |||
|
|||
<xsd:element name="metrics-plugin" maxOccurs="1" minOccurs="0"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the test version of artemis-configuration.xsd updated also, to keep them in sync.
It would be nice now to have the components repo, and an option to include it with the installation. is this ready to merge? |
} | ||
|
||
public void unregisterMeters(MetricsManager metricsManager) { | ||
if (metricsManager != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that metricsManager
is volatile, it should be called in a local variable and if not null, used to call its method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ups sorry, reading it from the web I haven't noticed from where metricsManager
was coming
.description(description) | ||
.register(meterRegistry); | ||
|
||
meters.add(meter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This risk to be called by different threads: take a look to the implementation I've posted above (that is using the compute method, that is thread safe for a specific key).
I just pushed the latest changes:
|
@jbertram there still work to be improved, mainly when we get the repository, but this is good as is for now. We can send more commits later. |
**Address** | ||
|
||
- routed.message.count | ||
- unrouted.message.count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jbertram what about paging size? This is quite important thing to watch out for.
Based on the conversation from #2626 I decided to pursue Micrometer integration which required a completely different approach than the one I took before. There is now no direct integration with Prometheus. The broker uses Micrometer APIs and a plugin structure was added to support specific integrations. A Prometheus plugin is available at https://github.com/jbertram/artemis-prometheus-metrics-plugin.