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

eu.xenit.alfred.telemetry.binder.cache.Hazelcast2CacheMetrics Maven / Gradle / Ivy

package eu.xenit.alfred.telemetry.binder.cache;

import com.hazelcast.core.IMap;
import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.FunctionTimer;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.cache.CacheMeterBinder;
import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics;
import io.micrometer.core.lang.Nullable;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link HazelcastCacheMetrics} equivalent which is compatible with Hazelcast 2 (Alfresco 5.x)
 */
public class Hazelcast2CacheMetrics extends CacheMeterBinder {

    private static final Logger LOGGER = LoggerFactory.getLogger(Hazelcast2CacheMetrics.class);

    private final IMap cache;

    /**
     * Record metrics on a Hazelcast cache.
     *
     * @param registry The registry to bind metrics to.
     * @param cache The cache to instrument.
     * @param tags Tags to apply to all recorded metrics. Must be an even number of arguments representing key/value
     * pairs of tags.
     * @param  The cache type.
     * @param  The cache key type.
     * @param  The cache value type.
     * @return The instrumented cache, unchanged. The original cache is not wrapped or proxied in any way.
     */
    public static > C monitor(MeterRegistry registry, C cache, String... tags) {
        return monitor(registry, cache, Tags.of(tags));
    }

    /**
     * Record metrics on a Hazelcast cache.
     *
     * @param registry The registry to bind metrics to.
     * @param cache The cache to instrument.
     * @param tags Tags to apply to all recorded metrics.
     * @param  The cache type.
     * @param  The cache key type.
     * @param  The cache value type.
     * @return The instrumented cache, unchanged. The original cache is not wrapped or proxied in any way.
     */
    public static > C monitor(MeterRegistry registry, C cache, Iterable tags) {
        new Hazelcast2CacheMetrics(cache, tags).bindTo(registry);
        return cache;
    }

    public > Hazelcast2CacheMetrics(C cache, Iterable tags) {
        super(cache, cache.getName(), tags);
        this.cache = cache;
    }

    @Override
    protected Long size() {
        return cache.getLocalMapStats().getOwnedEntryCount();
    }

    /**
     * @return The number of hits against cache entries hold in this local partition. Not all gets had to result from a
     * get operation against {@link #cache}. If a get operation elsewhere in the cluster caused a lookup against an
     * entry held in this partition, the hit will be recorded against map stats in this partition and not in the map
     * stats of the calling {@link IMap}.
     */
    @Override
    protected long hitCount() {
        return cache.getLocalMapStats().getHits();
    }

    /**
     * @return There is no way to calculate miss count in Hazelcast. See issue #586.
     */
    @Override
    protected Long missCount() {
        return null;
    }

    @Nullable
    @Override
    protected Long evictionCount() {
        return null;
    }

    @Override
    protected long putCount() {
        return cache.getLocalMapStats().getPutOperationCount();
    }

    @Override
    protected void bindImplementationSpecificMetrics(@Nonnull MeterRegistry registry) {
        Gauge.builder("cache.entries", cache, cache -> cache.getLocalMapStats().getBackupEntryCount())
                .tags(getTagsWithCacheName()).tag("ownership", "backup")
                .description("The number of backup entries held by this member")
                .register(registry);

        Gauge.builder("cache.entries", cache, cache -> cache.getLocalMapStats().getOwnedEntryCount())
                .tags(getTagsWithCacheName()).tag("ownership", "owned")
                .description("The number of owned entries held by this member")
                .register(registry);

        Gauge.builder("cache.entry.memory", cache, cache -> cache.getLocalMapStats().getBackupEntryMemoryCost())
                .tags(getTagsWithCacheName()).tag("ownership", "backup")
                .description("Memory cost of backup entries held by this member")
                .baseUnit("bytes")
                .register(registry);

        Gauge.builder("cache.entry.memory", cache, cache -> cache.getLocalMapStats().getOwnedEntryMemoryCost())
                .tags(getTagsWithCacheName()).tag("ownership", "owned")
                .description("Memory cost of owned entries held by this member")
                .baseUnit("bytes")
                .register(registry);

        FunctionCounter.builder("cache.partition.gets", cache,
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getNumberOfGets"))
                .tags(getTagsWithCacheName())
                .description("The total number of get operations executed against this partition")
                .register(registry);

        timings(registry);
        // nearCacheMetrics(registry); not available in Hazelcast 2.4
    }

    private void timings(MeterRegistry registry) {
        FunctionTimer.builder("cache.gets.latency", cache,
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getNumberOfGets"),
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getTotalGetLatency"),
                TimeUnit.NANOSECONDS)
                .tags(getTagsWithCacheName())
                .description("Cache gets")
                .register(registry);

        FunctionTimer.builder("cache.puts.latency", cache,
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getNumberOfPuts"),
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getTotalPutLatency"),
                TimeUnit.NANOSECONDS)
                .tags(getTagsWithCacheName())
                .description("Cache puts")
                .register(registry);

        FunctionTimer.builder("cache.removals.latency", cache,
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getNumberOfRemoves"),
                cache -> extractMetricWithReflection(cache.getLocalMapStats(), "getOperationStats", "getTotalRemoveLatency"),
                TimeUnit.NANOSECONDS)
                .tags(getTagsWithCacheName())
                .description("Cache removals")
                .register(registry);
    }

    public static long extractMetricWithReflection(final Object object, final String... methods) {
        try {
            Object currentObject = object;
            for (String methodToExecute : methods) {
                final Method method = currentObject.getClass().getMethod(methodToExecute);
                method.setAccessible(true);
                currentObject = method.invoke(currentObject);
            }
            return (long) currentObject;
        } catch (Throwable e) {
            LOGGER.warn("Unable to extract metric using reflection", e);
            return -1;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy