Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions clients/src/main/java/org/apache/kafka/common/metrics/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,10 @@ public void addMetric(MetricName metricName, MetricConfig config, MetricValuePro
Objects.requireNonNull(metricValueProvider),
config == null ? this.config : config,
time);
registerMetric(m);
KafkaMetric existingMetric = registerMetric(m);

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.

Could we clarify in the javadoc above that if the metric already exists, an exception would be thrown?

if (existingMetric != null) {
throw new IllegalArgumentException("A metric named '" + metricName + "' already exists, can't register another one.");
}
}

/**
Expand All @@ -524,6 +527,26 @@ public void addMetric(MetricName metricName, MetricValueProvider<?> metricValueP
addMetric(metricName, null, metricValueProvider);
}

/**
Comment thread
vamossagar12 marked this conversation as resolved.
Outdated
* Create or get an existing metric to monitor an object that implements MetricValueProvider.
* This metric won't be associated with any sensor. This is a way to expose existing values as metrics.
* This method takes care of synchronisation while updating/accessing metrics by concurrent threads.
*
* @param metricName The name of the metric
* @param metricValueProvider The metric value provider associated with this metric
* @return Existing KafkaMetric if already registered or else a newly created one
*/
public KafkaMetric addMetricIfAbsent(MetricName metricName, MetricConfig config, MetricValueProvider<?> metricValueProvider) {
KafkaMetric metric = new KafkaMetric(new Object(),
Objects.requireNonNull(metricName),
Objects.requireNonNull(metricValueProvider),
config == null ? this.config : config,
time);

KafkaMetric existingMetric = registerMetric(metric);
return existingMetric == null ? metric : existingMetric;
}

/**
* Remove a metric if it exists and return it. Return null otherwise. If a metric is removed, `metricRemoval`
* will be invoked for each reporter.
Expand Down Expand Up @@ -563,10 +586,18 @@ public synchronized void removeReporter(MetricsReporter reporter) {
}
}

synchronized void registerMetric(KafkaMetric metric) {
/**
* Register a metric if not present or return an already existing metric otherwise.
* When a metric is newly registered, this method returns null
*
* @param metric The KafkaMetric to register
* @return KafkaMetric if the metric already exists, null otherwise

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.

nit: we can just say "the existing metric with the same name"

*/
synchronized KafkaMetric registerMetric(KafkaMetric metric) {
Comment thread
vamossagar12 marked this conversation as resolved.
Outdated
MetricName metricName = metric.metricName();
if (this.metrics.containsKey(metricName))
throw new IllegalArgumentException("A metric named '" + metricName + "' already exists, can't register another one.");
if (this.metrics.containsKey(metricName)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not efficient to do contains and then get. We should do the get and check if the result is null. That halves the cost of the operations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Or even better, call putIfAbsent, if available.

return this.metrics.get(metricName);
}
this.metrics.put(metricName, metric);
for (MetricsReporter reporter : reporters) {
try {
Expand All @@ -576,6 +607,7 @@ synchronized void registerMetric(KafkaMetric metric) {
}
}
log.trace("Registered metric named {}", metricName);
return null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,10 @@ public synchronized boolean add(CompoundStat stat, MetricConfig config) {
for (NamedMeasurable m : stat.stats()) {
final KafkaMetric metric = new KafkaMetric(lock, m.name(), m.stat(), statConfig, time);
if (!metrics.containsKey(metric.metricName())) {
registry.registerMetric(metric);
KafkaMetric existingMetric = registry.registerMetric(metric);
if (existingMetric != null) {
throw new IllegalArgumentException("A metric named '" + metric.metricName() + "' already exists, can't register another one.");
}
metrics.put(metric.metricName(), metric);
}
}
Expand Down Expand Up @@ -336,7 +339,10 @@ public synchronized boolean add(final MetricName metricName, final MeasurableSta
statConfig,
time
);
registry.registerMetric(metric);
KafkaMetric existingMetric = registry.registerMetric(metric);
if (existingMetric != null) {
throw new IllegalArgumentException("A metric named '" + metricName + "' already exists, can't register another one.");
}
metrics.put(metric.metricName(), metric);
stats.add(new StatAndConfig(Objects.requireNonNull(stat), metric::config));
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,7 @@ Map<String, String> tags() {
*/
public <T> void addValueMetric(MetricNameTemplate nameTemplate, final LiteralSupplier<T> supplier) {
MetricName metricName = metricName(nameTemplate);
if (metrics().metric(metricName) == null) {
metrics().addMetric(metricName, (Gauge<T>) (config, now) -> supplier.metricValue(now));
}
metrics().addMetricIfAbsent(metricName, null, (Gauge<T>) (config, now) -> supplier.metricValue(now));
}

/**
Expand All @@ -333,9 +331,7 @@ public <T> void addValueMetric(MetricNameTemplate nameTemplate, final LiteralSup
*/
public <T> void addImmutableValueMetric(MetricNameTemplate nameTemplate, final T value) {
MetricName metricName = metricName(nameTemplate);
if (metrics().metric(metricName) == null) {
metrics().addMetric(metricName, (Gauge<T>) (config, now) -> value);
}
metrics().addMetricIfAbsent(metricName, null, (Gauge<T>) (config, now) -> value);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
Expand Down Expand Up @@ -84,6 +85,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.powermock.api.easymock.PowerMock.createMock;

@RunWith(PowerMockRunner.class)
Expand Down Expand Up @@ -497,6 +500,17 @@ public void shouldAddNewStoreLevelMutableMetric() {
verify(metrics);
}

@Test
public void shouldCreateNewStoreLevelMutableMetric() {
final MetricName metricName =
new MetricName(METRIC_NAME1, STATE_STORE_LEVEL_GROUP, DESCRIPTION1, STORE_LEVEL_TAG_MAP);
final MetricConfig metricConfig = new MetricConfig().recordLevel(INFO_RECORDING_LEVEL);
final Metrics metrics = new Metrics(metricConfig);
assertNull(metrics.metric(metricName));
metrics.addMetricIfAbsent(metricName, metricConfig, VALUE_PROVIDER);
assertNotNull(metrics.metric(metricName));
}

@Test
public void shouldNotAddStoreLevelMutableMetricIfAlreadyExists() {
final Metrics metrics = mock(Metrics.class);
Expand All @@ -521,6 +535,38 @@ public void shouldNotAddStoreLevelMutableMetricIfAlreadyExists() {
verify(metrics);
}

@Test
public void shouldReturnSameMetricIfAlreadyCreated() {
final MetricName metricName =
new MetricName(METRIC_NAME1, STATE_STORE_LEVEL_GROUP, DESCRIPTION1, STORE_LEVEL_TAG_MAP);
final MetricConfig metricConfig = new MetricConfig().recordLevel(INFO_RECORDING_LEVEL);
final Metrics metrics = new Metrics(metricConfig);
assertNull(metrics.metric(metricName));
final KafkaMetric kafkaMetric = metrics.addMetricIfAbsent(metricName, metricConfig, VALUE_PROVIDER);
assertEquals(kafkaMetric, metrics.addMetricIfAbsent(metricName, metricConfig, VALUE_PROVIDER));
}

@Test
public void shouldCreateMetricOnceDuringConcurrentMetricCreationRequest() throws InterruptedException {
final MetricName metricName =
new MetricName(METRIC_NAME1, STATE_STORE_LEVEL_GROUP, DESCRIPTION1, STORE_LEVEL_TAG_MAP);
final MetricConfig metricConfig = new MetricConfig().recordLevel(INFO_RECORDING_LEVEL);
final Metrics metrics = new Metrics(metricConfig);
assertNull(metrics.metric(metricName));
final AtomicReference<KafkaMetric> metricCreatedViaThread1 = new AtomicReference<>();
final AtomicReference<KafkaMetric> metricCreatedViaThread2 = new AtomicReference<>();

final Thread thread1 = new Thread(() -> metricCreatedViaThread1.set(metrics.addMetricIfAbsent(metricName, metricConfig, VALUE_PROVIDER)));
final Thread thread2 = new Thread(() -> metricCreatedViaThread2.set(metrics.addMetricIfAbsent(metricName, metricConfig, VALUE_PROVIDER)));

thread1.start();
thread2.start();

thread1.join();
thread2.join();
assertEquals(metricCreatedViaThread1.get(), metricCreatedViaThread2.get());
}

@Test
public void shouldRemoveStateStoreLevelSensors() {
final Metrics metrics = niceMock(Metrics.class);
Expand Down