openrewrite / rewrite-micrometer

OpenRewrite recipes for Micrometer.
Apache License 2.0
1 stars 3 forks source link

Micrometer Timer to Micrometer Observation conversion #1

Open marcingrzejszczak opened 1 year ago

marcingrzejszczak commented 1 year ago

What problem are you trying to solve?

Convert Micrometer Timer to a Micrometer Observation (cc @shakuzen @jonatan-ivanov).

NOTE: This feature request is temporarily set in rewrite-spring but since it's related to Micrometer it has nothing to do with Spring.

What precondition(s) should be checked before applying this recipe?

Java projects.

Describe the situation before applying the recipe

.Example using Timer Builder

class Example2 {

        private final MeterRegistry registry;

        Example2(MeterRegistry registry) {
            this.registry = registry;
        }

        void run() {
            Timer.builder("foo")
                    .tag("lowTag", "lowTagValue")
                    .register(registry)
                    .record(() -> System.out.println("Hello"));
        }

    }

.Example using Sample

    class Example3 {

        private final MeterRegistry registry;

        Example3(MeterRegistry registry) {
            this.registry = registry;
        }

        void run() {
            Timer.Sample sample = Timer.start(registry);
            System.out.println("Hello");
            sample.stop(Timer.builder("foo")
                    .tag("lowTag", "lowTagValue")
                    .register(registry));
        }

    }

.Example using Wrap + Run


    class Example4 {

        private final MeterRegistry registry;

        Example4(MeterRegistry registry) {
            this.registry = registry;
        }

        void run() {
            Timer.builder("foo")
                    .tag("lowTag", "lowTagValue")
                    .register(registry)
                    .wrap(() -> System.out.println("Hello"))
                    .run();
        }

    }

.Example for different timers in the same class


    static class ExampleWithDifferentMeters {

        private final MeterRegistry registry;

        ExampleWithDifferentMeters(MeterRegistry registry) {
            this.registry = registry;
        }

        void run() {
            DistributionSummary summary = DistributionSummary.builder("summary")
                    .register(registry);

            Counter counter = Counter.builder("counter outside of timer").register(registry);

            Timer.builder("foo")
                    .tag("lowTag", "lowTagValue")
                    .register(registry)
                    .record(() -> {
                        Counter counter2 = Counter.builder("counter inside of timer").register(registry);
                        counter2.increment();
                        System.out.println("Hello");
                    });

            counter.increment(10);

            summary.record(100);
        }

    }

Describe the situation after applying the recipe

. Solution for Example2, Example3, Example4

class Example {

    private final ObservationRegistry registry;

    Example(ObservationRegistry registry) {
        this.registry = registry;
    }

    void run() {
        Observation.createNotStarted("foo", registry)
                .lowCardinalityKeyValue("lowTag", "lowTagValue")
                .observe(() -> System.out.println("Hello"));
    }

}

. Solution for ExampleWithDifferentMeters

class ExampleWithDifferentMetersWithObservation {

        private final ObservationRegistry observationRegistry;

        private final MeterRegistry registry;

        ExampleWithDifferentMetersWithObservation(ObservationRegistry observationRegistry, MeterRegistry registry) {
            this.observationRegistry = observationRegistry;
            this.registry = registry;
        }

        void run() {
            DistributionSummary summary = DistributionSummary.builder("summary")
                    .register(registry);

            Counter counter = Counter.builder("counter outside of timer").register(registry);

            Observation observation = Observation.createNotStarted("foo", observationRegistry)
                    .lowCardinalityKeyValue("lowTag", "lowTagValue");

            observation.observe(() -> {
                observation.event(() -> "counter inside of timer");
                System.out.println("Hello");
            });

            counter.increment(10);

            summary.record(100);
        }

    }

Have you considered any alternatives or workarounds?

No workarounds at this point that I'm aware of.

Any additional context

If we're scanning users' business code then most likely all timers are related to business operations that should be within an actual tracing scope. That means that conversion from a timer to an observation with having e.g. Micrometer Tracing on the classpath will result in creation of a child span and that's a great outcome.

The problem we can face is if there are any timers in infrastructure part of the code. Those might maybe be kept as timers (e.g. time required to start the application - we don't want that to become a span). I doubt however that we are able to get this information from scanning the code.

This recipe should be applicable only if micrometer-core JAR is on the classpath (that contains the Timer class). If we want to migrate to Micrometer Observation then micrometer-observation JAR must be present on the classpath.

Check the Micrometer Docs for more information about Micrometer Observation.

Are you interested in contributing this recipe to OpenRewrite?

We can definitely help with guidance on how to do this feature.

timtebeek commented 1 year ago

Discussed internally just now: we'll indeed move this item and any related non-spring-specific recipes into a separate rewrite-micrometer module.

timtebeek commented 1 year ago

Quick note to say I'm reading up, and have started to define the scenarios (not) to convert in #2; welcome to follow along and chime in there with further cases to cover.