All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mantledillusion.metrics.trail.MetricsTrailSupport Maven / Gradle / Ivy

There is a newer version: 2.2.2
Show newest version
package com.mantledillusion.metrics.trail;

import com.mantledillusion.metrics.trail.api.Metric;

import java.lang.ref.WeakReference;
import java.util.*;
import java.util.function.Supplier;

/**
 * Universal support for {@link Thread} based {@link MetricsTrail}s using {@link ThreadLocal}.
 */
public final class MetricsTrailSupport {

    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>();
    private static final Map> TRAIL_LISTENERS = new HashMap<>();

    private MetricsTrailSupport() {}

    /**
     * Adds a listener to announce the begin and end of a trail to.
     *
     * @param listener The listener to add; might not be null.
     * @param mode The mode in which the listener should be statically referenced; might not be null.
     */
    public static void addListener(MetricsTrailListener listener, MetricsTrailListener.ReferenceMode mode) {
        registerListener(System.identityHashCode(listener), listener, mode);
    }

    /**
     * Removes a previously added listener.
     *
     * @param listener The listener, might be null.
     */
    public static void removeListener(MetricsTrailListener listener) {
        unregisterListener(System.identityHashCode(listener));
    }

    /**
     * Adds the given consumer as a permanent consumer to hook to all trails occurring.
     *
     * @param consumer The consumer to add; might not be null.
     * @param mode The mode in which the listener should be statically referenced; might not be null.
     */
    public static void addPersistentHook(MetricsTrailConsumer consumer, MetricsTrailListener.ReferenceMode mode) {
        if (consumer == null) {
            throw new IllegalArgumentException("Cannot hook a null consumer");
        }
        registerListener(System.identityHashCode(consumer), (trail, eventType) -> {
            if (eventType == MetricsTrailListener.EventType.BEGIN) {
                trail.hook(consumer);
            }
        }, mode);
    }

    /**
     * Removes a previously added consumer.
     *
     * @param consumer The consumer, might be null.
     */
    public static void removePersistentHook(MetricsTrailConsumer consumer) {
        unregisterListener(System.identityHashCode(consumer));
    }

    private static void registerListener(Integer id, MetricsTrailListener listener, MetricsTrailListener.ReferenceMode mode) {
        synchronized (TRAIL_LISTENERS) {
            if (listener == null) {
                throw new IllegalArgumentException("Cannot add a null listener.");
            } else  if (mode == null) {
                throw new IllegalArgumentException("Cannot add a listener with a null mode.");
            }
            Supplier listenerSupplier;
            switch (mode) {
                case HARD:
                    listenerSupplier = () -> listener;
                    break;
                case WEAK:
                    WeakReference weakReference =  new WeakReference<>(listener);
                    listenerSupplier = weakReference::get;
                    break;
                default:
                    throw new IllegalStateException("Unexpected reference mode: " + mode);
            }
            TRAIL_LISTENERS.put(id, listenerSupplier);
        }
    }

    private static void unregisterListener(Integer id) {
        synchronized (TRAIL_LISTENERS) {
            TRAIL_LISTENERS.remove(id);
        }
    }

    /**
     * Returns whether the calling {@link Thread} is identified by a {@link MetricsTrail}.
     *
     * @return True if the current {@link Thread} is identified by a {@link MetricsTrail}, false otherwise
     */
    public static boolean has() {
        return THREAD_LOCAL.get() != null;
    }

    /**
     * Begins a {@link MetricsTrail} on the current thread using a random {@link UUID}.
     * 

* Will cause a {@link MetricsTrailListener.EventType#BEGIN} event to be dispatched. * * @return The random UUID of the newly created {@link MetricsTrail}, never null * @throws IllegalStateException If the current {@link Thread} already is identified by a {@link MetricsTrail}, * which can be checked using {@link #has()}. */ public static synchronized UUID begin() throws IllegalStateException { UUID correlationId = UUID.randomUUID(); begin(correlationId); return correlationId; } /** * Begins a {@link MetricsTrail} on the current thread using the given {@link UUID}. *

* Will cause a {@link MetricsTrailListener.EventType#BEGIN} event to be dispatched. * * @param correlationId The {@link UUID} to identify the new {@link MetricsTrail} by; might not be null. * @throws IllegalStateException If the current {@link Thread} is already identified by a {@link MetricsTrail}, * which can be checked using {@link #has()}. */ public static void begin(UUID correlationId) throws IllegalStateException { if (correlationId == null) { throw new IllegalArgumentException("Cannot begin trail using a null thread id"); } bind(new MetricsTrail(correlationId), MetricsTrailListener.EventType.BEGIN); } /** * Binds a pre-existing {@link MetricsTrail} on the current thread. *

* Will cause a {@link MetricsTrailListener.EventType#BIND} event to be dispatched. * * @param metricsTrail The {@link MetricsTrail}; might not be null. * @throws IllegalStateException If the current {@link Thread} is already identified by a {@link MetricsTrail}, * which can be checked using {@link #has()}. */ public static void bind(MetricsTrail metricsTrail) throws IllegalStateException { bind(metricsTrail, MetricsTrailListener.EventType.BIND); } private static void bind(MetricsTrail metricsTrail, MetricsTrailListener.EventType eventType) { if (metricsTrail == null) { throw new IllegalArgumentException("Cannot hook a null metrics trail."); } else if (THREAD_LOCAL.get() != null) { throw new IllegalStateException("Cannot begin trail " + metricsTrail.getCorrelationId() + " for thread " + Thread.currentThread() + "; the current thread is already identified by trail " + THREAD_LOCAL.get().getCorrelationId()); } THREAD_LOCAL.set(metricsTrail); announce(metricsTrail, eventType); } /** * Returns the {@link UUID} of the {@link MetricsTrail} that identifies the current {@link Thread}. * * @return The ID of the current {@link MetricsTrail}, never null * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static UUID id() throws IllegalStateException { return get().getCorrelationId(); } /** * Returns the {@link MetricsTrail} that identifies the current {@link Thread}. * * @return Th current {@link MetricsTrail}, never null * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static MetricsTrail get() throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot retrieve the ID of the current trail; current thread is not identified by one"); } return THREAD_LOCAL.get(); } /** * Hooks the given {@link MetricsTrailConsumer} to the current {@link Thread}s {@link MetricsTrail}. *

* To hook the given {@link MetricsTrailConsumer} to the {@link MetricsTrail}, a new * {@link MetricsTrailConsumer.MetricsTrailConsumerQueue} is created that will receive the trail's {@link Metric}s * and enqueue them for delivery for the consumer. *

* Delivering the {@link Metric}s will depend on the settings made to the {@link MetricsTrailConsumer}. * * @param consumer The {@link MetricsTrailConsumer} hook; might not be null. * @return A new {@link MetricsTrailConsumer.MetricsTrailConsumerQueue}, never null * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static MetricsTrailConsumer.MetricsTrailConsumerQueue hook(MetricsTrailConsumer consumer) throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot retrieve whether the current trail has gated metrics; current thread is not identified by one"); } return THREAD_LOCAL.get().hook(consumer); } /** * Commits the given {@link Metric} to all {@link MetricsTrailConsumer.MetricsTrailConsumerQueue}s hooked to the current {@link Thread}'s {@link MetricsTrail}. * * @param metric The metric to commit; might not be null. * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static void commit(Metric metric) throws IllegalStateException { commit(metric, true); } /** * Commits the given {@link Metric} to all {@link MetricsTrailConsumer.MetricsTrailConsumerQueue}s hooked to the current {@link Thread}'s {@link MetricsTrail}. * * @param metric The metric to commit; might not be null. * @param forced True if committing the given {@link Metric} is inevitable, so if the current {@link Thread} is not identified by a trail, an {@link IllegalStateException} has to be thrown. * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail} and the commit is forced. */ public static void commit(Metric metric, boolean forced) throws IllegalStateException { MetricsTrail trail = THREAD_LOCAL.get(); if (trail != null) { trail.commit(metric); } else if (forced) { throw new IllegalStateException("Cannot commit the given metric to the current trail; current thread is not identified by one"); } } /** * Returns whether there are {@link Metric}s that are enqueued and waiting for any of the current {@link Thread} * {@link MetricsTrail}'s {@link MetricsTrailConsumer.MetricsTrailConsumerQueue} gates to open so they can be delivered. * * @return True if there is at least one {@link Metric} currently gated, false otherwise * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static boolean hasGated() throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot retrieve whether the current trail has gated metrics; current thread is not identified by one"); } return THREAD_LOCAL.get().hasGated(); } /** * Returns whether there are {@link Metric}s of the current {@link Thread} {@link MetricsTrail}'s * {@link MetricsTrailConsumer.MetricsTrailConsumerQueue} currently being delivered to their consumer by asynchronous tasks. * * @return True if there is at least one {@link Metric} currently being delivered, false otherwise * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static boolean isDelivering() throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot retrieve whether the current trail is currently delivering metrics; current thread is not identified by one"); } return THREAD_LOCAL.get().isDelivering(); } /** * Ends the {@link MetricsTrail} that identifies the current {@link Thread}. *

* Will cause a {@link MetricsTrailListener.EventType#END} event to be dispatched. * * @return The {@link UUID} of the current {@link Thread}'s trail, never null * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static UUID end() throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot end trail; current thread is not identified by one"); } MetricsTrail metricsTrail = THREAD_LOCAL.get(); end(metricsTrail); THREAD_LOCAL.set(null); return metricsTrail.getCorrelationId(); } /** * Ends the {@link MetricsTrail} that identifies the current {@link Thread}. *

* Will cause a {@link MetricsTrailListener.EventType#END} event to be dispatched. * * @param metricsTrail The trail to release; might not be null. * @throws IllegalStateException If the given {@link MetricsTrail} is null. */ public static void end(MetricsTrail metricsTrail) throws IllegalStateException { if (metricsTrail == null) { throw new IllegalArgumentException("Cannot release a null trail"); } announce(metricsTrail, MetricsTrailListener.EventType.END); metricsTrail.end(); } /** * Releases the {@link MetricsTrail} that identifies the current {@link Thread} without ending it. *

* Will cause a {@link MetricsTrailListener.EventType#RELEASE} event to be dispatched. * * @return The {@link MetricsTrail} of the current {@link Thread}, never null * @throws IllegalStateException If the current {@link Thread} is not identified by a {@link MetricsTrail}. */ public static MetricsTrail release() throws IllegalStateException { if (THREAD_LOCAL.get() == null) { throw new IllegalStateException("Cannot end trail; current thread is not identified by one"); } MetricsTrail metricsTrail = THREAD_LOCAL.get(); release(metricsTrail); THREAD_LOCAL.set(null); return metricsTrail; } /** * Releases the {@link MetricsTrail} that identifies the current {@link Thread} without ending it. *

* Will cause a {@link MetricsTrailListener.EventType#RELEASE} event to be dispatched. * * @param metricsTrail The trail to release; might not be null. * @throws IllegalStateException If the given {@link MetricsTrail} is null. */ public static void release(MetricsTrail metricsTrail) throws IllegalStateException { if (metricsTrail == null) { throw new IllegalArgumentException("Cannot release a null trail"); } announce(metricsTrail, MetricsTrailListener.EventType.RELEASE); } private static void announce(MetricsTrail metricsTrail, MetricsTrailListener.EventType eventType) { synchronized (TRAIL_LISTENERS) { Iterator> iterator = TRAIL_LISTENERS.values().iterator(); while (iterator.hasNext()) { MetricsTrailListener listener = iterator.next().get(); if (listener == null) { iterator.remove(); } else { try { listener.announce(metricsTrail, eventType); } catch (Exception e) { System.out.println("Encountered an error while trying to announce a " + eventType.name() + " trail " + metricsTrail.getCorrelationId() + " to listener " + listener + ": " + e.getMessage()); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy