Skip to content

Conversation

majialoong
Copy link
Contributor

KIP-188 introduced MetricValueProvider, adding Measurable and Gauge as its sub interfaces. However, this left a legacy issue: move the value method from Gauge to the super interface, MetricValueProvider.

This PR moves the value method from Gauge to MetricValueProvider and provides a default implementation in Measurable. This unifies the methods used by Gauge and Measurable to obtain monitoring values.

@github-actions github-actions bot added triage PRs from the community clients small Small PRs labels Sep 16, 2025
@majialoong
Copy link
Contributor Author

For binary compatibility issues, the following tests were performed:

  1. Binary compatibility tested using japicmp

kafka-clients-4.2.0-SNAPSHOT.jar is the code before this PR, and kafka-clients-4.2.0-SNAPSHOT-new.jar contains the changed code of this PR.

image
  1. Write a test code to verify
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.metrics.Gauge;
import org.apache.kafka.common.metrics.MetricConfig;
import org.apache.kafka.common.metrics.Metrics;
import org.apache.kafka.common.metrics.stats.Min;

public class CustomMetrics {
    public static void main(String[] args) {
        Metrics metrics = new Metrics();

        MetricName gaugeMetricNameOne = new MetricName("gaugeMetricNameOne", "test-metrics", "description for gauge "
                + "metric "
                + "one",
                new HashMap<>());
        metrics.addMetric(gaugeMetricNameOne, (Gauge<String>) (config, now) -> "hello gauge.");

        MetricName gaugeMetricNameTwo = new MetricName("gaugeMetricNameTwo", "test-metrics", "description for gauge "
                + "metric two",
                new HashMap<>());
        metrics.addMetric(gaugeMetricNameTwo, (Gauge<Integer>) (config, now) -> 123);

        MetricName measurableMetricNameOne = new MetricName("measurableMetricNameOne", "test-metrics", "description for "
                + " measurable metric one",
                new HashMap<>());
        metrics.addMetric(measurableMetricNameOne, (config, now) -> 100L);

        MetricName measurableMetricNameTwo = new MetricName("measurableMetricNameTwo", "test-metrics", "description "
                + "for measurable metric two",
                new HashMap<>());
        Min min = new Min();
        long windowMs = 100;
        int samples = 2;
        MetricConfig config = new MetricConfig().timeWindow(windowMs, TimeUnit.MILLISECONDS).samples(samples);
        min.record(config, 50, System.currentTimeMillis());
        metrics.addMetric(measurableMetricNameTwo, min);

        System.out.println("gaugeMetricNameOne: " + metrics.metric(gaugeMetricNameOne).metricValue());
        System.out.println("gaugeMetricNameTwo: " + metrics.metric(gaugeMetricNameTwo).metricValue());
        System.out.println("measurableMetricNameOne: " + metrics.metric(measurableMetricNameOne).metricValue());
        System.out.println("measurableMetricNameTwo: " + metrics.metric(measurableMetricNameTwo).metricValue());
    }
}

Compile using the code in the current trunk branch and version 1.0.1. Then run using the code that includes the changes in this PR.

trunk beanch :
image

1.0.1 version :
image

@majialoong
Copy link
Contributor Author

Hello, @ijuma @rajinisivaram @chia7712 , when have time, please review this PR , thanks !

Copy link
Member

@chia7712 chia7712 left a comment

Choose a reason for hiding this comment

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

@majialoong thanks for this patch!

return ((Gauge<?>) metricValueProvider).value(config, now);
else
throw new IllegalStateException("Not a valid metric: " + this.metricValueProvider.getClass());
return metricValueProvider.value(config, now);
Copy link
Member

Choose a reason for hiding this comment

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

Can we update the docs so they match these changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the reminder, I will update this doc.

@majialoong majialoong requested a review from chia7712 September 17, 2025 16:25
@github-actions github-actions bot removed the triage PRs from the community label Sep 18, 2025
T value(MetricConfig config, long now);

}
public interface Gauge<T> extends MetricValueProvider<T> { }
Copy link
Member

Choose a reason for hiding this comment

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

The Gauge type seems redundant at this stage. Would it be worth filing a KIP to deprecate it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Keeping the Gauge type can improves code readability. Readers immediately understand that the monitoring metric represents an instantaneous value, and it can also be equated with the concept of Gauge in mainstream monitoring systems. Directly using the MetricValueProvider may be a bit too abstract.

Copy link
Member

Choose a reason for hiding this comment

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

Another follow-up (KIP) could be removing addMetric(MetricName metricName, Measurable measurable), which would let us drop the explicit type from the lambda
before

metrics.addMetric(metricName(metrics, "version", Map.of()), (Gauge<String>) (config, now) -> appInfo.getVersion());

after

metrics.addMetric(metricName(metrics, "version", Map.of()),  (config, now) -> appInfo.getVersion());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good suggestion, I've created a JIRA to track this issue:https://issues.apache.org/jira/browse/KAFKA-19729

@ijuma
Copy link
Member

ijuma commented Sep 21, 2025

Thanks for the PR. Regarding compatibility, you'd want to test both binary and source compatibility.

@majialoong
Copy link
Contributor Author

Thanks for the PR. Regarding compatibility, you'd want to test both binary and source compatibility.

@ijuma Thank you for your comments. In the above conversation, I listed two methods for testing binary compatibility. If there are any scenarios I've overlooked, please comment and add them. I'll test and verify them promptly. I will also conduct some further testing and verification and add to this in subsequent conversations.

@majialoong
Copy link
Contributor Author

majialoong commented Sep 26, 2025

I performed the following tests and found no binary or source incompatibilities.

To test compatibility, I wrote the following test code, which includes code demonstrating the usage of Gauge and Measurable.

import org.apache.kafka.common.MetricName;
import org.apache.kafka.common.metrics.Gauge;
import org.apache.kafka.common.metrics.Measurable;
import org.apache.kafka.common.metrics.MetricConfig;
import org.apache.kafka.common.metrics.Metrics;
import org.apache.kafka.common.metrics.stats.Min;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;

public class TestCompatibility {
    public static void main(String[] args) {
        Metrics metrics = new Metrics();

        // Kafka about MetricValueProvider have two sub-interface Gauge and Measurable

        // test Gauge
        MetricName gaugeMetricNameOne = new MetricName("gaugeMetricNameOne", "test-metrics", "description for gauge "
                + "metric "
                + "one",
                new HashMap<>());
        metrics.addMetric(gaugeMetricNameOne, (Gauge<String>) (config, now) -> "hello gauge.");

        MetricName gaugeMetricNameTwo = new MetricName("gaugeMetricNameTwo", "test-metrics", "description for gauge "
                + "metric two",
                new HashMap<>());
        metrics.addMetric(gaugeMetricNameTwo, (Gauge<Integer>) (config, now) -> 123);

        Gauge<String> stringGauge = (config, now) -> "string gauge";
        MetricName gaugeMetricNameThree = new MetricName("gaugeMetricNameThree", "test-metrics", "description for gauge "
                + "metric three",
                new HashMap<>());
        metrics.addMetric(gaugeMetricNameThree, stringGauge);

        System.out.println("gaugeMetricNameOne: " + metrics.metric(gaugeMetricNameOne).metricValue());
        System.out.println("gaugeMetricNameTwo: " + metrics.metric(gaugeMetricNameTwo).metricValue());
        System.out.println("gaugeMetricNameThree: " + metrics.metric(gaugeMetricNameThree).metricValue());

        // test Measurable
        MetricName measurableMetricNameOne = new MetricName("measurableMetricNameOne", "test-metrics", "description for "
                + " measurable metric one",
                new HashMap<>());
        metrics.addMetric(measurableMetricNameOne, (config, now) -> 100L);

        MetricName measurableMetricNameTwo = new MetricName("measurableMetricNameTwo", "test-metrics", "description "
                + "for measurable metric two",
                new HashMap<>());

        Measurable measurable = (Measurable) (config, now) -> 200L;
        metrics.addMetric(measurableMetricNameTwo, measurable);

        MetricName measurableMetricNameThree = new MetricName("measurableMetricNameThree", "test-metrics", "description "
                + "for measurable metric three",
                new HashMap<>());

        // test Measurable implementation class Min
        Min min = new Min();
        long windowMs = 100;
        int samples = 2;
        MetricConfig config = new MetricConfig().timeWindow(windowMs, TimeUnit.MILLISECONDS).samples(samples);
        min.record(config, 50, System.currentTimeMillis());
        metrics.addMetric(measurableMetricNameThree, min);

        System.out.println("measurableMetricNameOne: " + metrics.metric(measurableMetricNameOne).metricValue());
        System.out.println("measurableMetricNameTwo: " + metrics.metric(measurableMetricNameTwo).metricValue());
        System.out.println("measurableMetricNameThree: " + metrics.metric(measurableMetricNameThree).metricValue());
    }
}

I have prepared three versions of the Kafka-clients JAR files :

  1. kafka-clients-1.0.1.jar(The Gauge and MetricValueProvider interfaces have been added(1.0.0 version), and KafkaMetric first time as public type(1.0.1 version).)
  2. kafka-clients-4.2.0-SNAPSHOT.jar(trunk branch code)
  3. kafka-clients-4.2.0-SNAPSHOT-new.jar (with this PR code)

Regarding source code compatibility, I tested the code using the three different versions of the JAR file, and it compiled successfully in all three cases.

image

Regarding binary compatibility, I compiled the test code using each of these three different versions of the JAR file, and all of them ran successfully in all three versions.

  1. Compile using version kafka-clients-1.0.1.jar,and run directly using the three versions of the JAR files.
image
  1. Compile using version kafka-clients-4.2.0-SNAPSHOT.jar,and run directly using the three versions of the JAR files.
image
  1. Compile using version kafka-clients-4.2.0-SNAPSHOT-new.jar,and run directly using the three versions of the JAR files.
image

Regarding binary compatibility testing, we also used japicmp for verification.

  1. Use the libs files generated by compiling the code included in this PR. The binary compatibility check using japicmp shows no changes.
image
  1. And use libs files were compiled using code from the trunk branch. The binary compatibility check using japicmp shows no changes.
image

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

Successfully merging this pull request may close these issues.

3 participants