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

com.ringcentral.platform.metrics.AbstractMeter Maven / Gradle / Ivy

package com.ringcentral.platform.metrics;

import com.ringcentral.platform.metrics.configs.MeterConfig;
import com.ringcentral.platform.metrics.configs.MeterInstanceConfig;
import com.ringcentral.platform.metrics.configs.MeterSliceConfig;
import com.ringcentral.platform.metrics.labels.Label;
import com.ringcentral.platform.metrics.labels.LabelValue;
import com.ringcentral.platform.metrics.labels.LabelValues;
import com.ringcentral.platform.metrics.labels.LabelValuesPredicate;
import com.ringcentral.platform.metrics.histogram.Histogram;
import com.ringcentral.platform.metrics.measurables.Measurable;
import com.ringcentral.platform.metrics.measurables.MeasurableValues;
import com.ringcentral.platform.metrics.names.MetricName;
import com.ringcentral.platform.metrics.utils.TimeMsProvider;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Collections.*;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toMap;
import static org.slf4j.LoggerFactory.getLogger;

public abstract class AbstractMeter<
    MI, // (Instance) Meter Impl
    IC extends MeterInstanceConfig,
    SC extends MeterSliceConfig,
    C extends MeterConfig> extends AbstractMetric implements Meter {

    public interface MeasurableValuesProvider {
        MeasurableValues measurableValues();
    }

    public interface MeasurableValueProvider {
        Object valueFor(MI meterImpl);
    }

    public interface MeasurableValueProvidersProvider<
        MI,
        IC extends MeterInstanceConfig,
        SC extends MeterSliceConfig,
        C extends MeterConfig> {

        Map> valueProvidersFor(
            IC instanceConfig,
            SC sliceConfig,
            C config,
            Set measurables);
    }

    public interface MeterImplMaker<
        MI,
        IC extends MeterInstanceConfig,
        SC extends MeterSliceConfig,
        C extends MeterConfig> {

        MI makeMeterImpl(
            IC instanceConfig,
            SC sliceConfig,
            C config,
            Set measurables,
            ScheduledExecutorService executor,
            MetricRegistry registry);
    }

    public interface InstanceMaker {
        AbstractMeterInstance makeInstance(
            MetricName name,
            List labelValues,
            boolean totalInstance,
            boolean labeledMetricTotalInstance,
            boolean levelInstance,
            Map> measurableValueProviders,
            MI meterImpl);

        AbstractExpirableMeterInstance makeExpirableInstance(
            MetricName name,
            List labelValues,
            boolean totalInstance,
            boolean labeledMetricTotalInstance,
            boolean levelInstance,
            Map> measurableValueProviders,
            MI meterImpl,
            long creationTimeMs);
    }

    public interface MeterImplUpdater {
        void update(MI meterImpl, long value);
    }

    static final long EXPIRED_INSTANCES_REMOVAL_ADDITIONAL_DELAY_MS = 10000L;

    private final AtomicBoolean removed = new AtomicBoolean();
    private final List listeners;

    private final Label[] labels;
    private final int labelCount;
    private final boolean labeledInstanceExpirationEnabled;
    private final boolean labeledInstanceAutoRemovalEnabled;
    private final long minLabeledInstanceExpirationTimeMs;
    private final LabelValuesPredicate exclusionPredicate;

    private final Slice allSlice;
    private final Slice[] slices;

    private final TimeMsProvider timeMsProvider;
    private final ScheduledExecutorService executor;

    private static final Logger logger = getLogger(AbstractMeter.class);

    @SuppressWarnings("unchecked")
    protected AbstractMeter(
        MetricName name,
        C config,
        MeasurableValueProvidersProvider measurableValueProvidersProvider,
        MeterImplMaker meterImplMaker,
        MeterImplUpdater meterImplUpdater,
        InstanceMaker instanceMaker,
        TimeMsProvider timeMsProvider,
        ScheduledExecutorService executor,
        MetricRegistry registry) {

        super(config.isEnabled(), name, config.description());
        this.timeMsProvider = timeMsProvider;

        if (!config.isEnabled()) {
            this.listeners = null;
            this.labels = null;
            this.labelCount = 0;
            this.labeledInstanceExpirationEnabled = false;
            this.labeledInstanceAutoRemovalEnabled = false;
            this.minLabeledInstanceExpirationTimeMs = 0L;
            this.exclusionPredicate = null;
            this.allSlice = null;
            this.slices = null;
            this.executor = null;

            return;
        }

        this.listeners = new ArrayList<>();

        if (config.hasLabels()) {
            this.labels = config.labels().toArray(new Label[0]);
            this.labelCount = this.labels.length;
            this.exclusionPredicate = config.exclusionPredicate();
            SC allSliceConfig = config.allSliceConfig();

            Set enabledSliceConfigs =
                config.hasSliceConfigs() ?
                config.sliceConfigs().stream().filter(MeterSliceConfig::isEnabled).collect(toCollection(LinkedHashSet::new)) :
                emptySet();

            this.labeledInstanceExpirationEnabled =
                (allSliceConfig.isEnabled() && allSliceConfig.isLabeledInstanceExpirationEnabled())
                || enabledSliceConfigs.stream().anyMatch(MeterSliceConfig::isLabeledInstanceExpirationEnabled);

            this.labeledInstanceAutoRemovalEnabled =
                this.labeledInstanceExpirationEnabled
                || (allSliceConfig.isEnabled() && allSliceConfig.hasEffectiveMaxLabeledInstances())
                || enabledSliceConfigs.stream().anyMatch(MeterSliceConfig::hasEffectiveMaxLabeledInstances);

            this.minLabeledInstanceExpirationTimeMs =
                this.labeledInstanceExpirationEnabled ?
                minLabeledInstanceExpirationTimeMs(allSliceConfig, enabledSliceConfigs) :
                0L;

            SliceContext sliceContext = new SliceContext<>(
                name,
                config,
                removed,
                listeners,
                labels,
                labelCount,
                measurableValueProvidersProvider,
                meterImplMaker,
                instanceMaker,
                meterImplUpdater,
                timeMsProvider,
                executor);

            this.allSlice =
                allSliceConfig.isEnabled() ?
                new Slice<>(sliceContext, allSliceConfig, registry) :
                null;

            this.slices =
                !enabledSliceConfigs.isEmpty() ?
                enabledSliceConfigs.stream().map(sc -> new Slice<>(sliceContext, sc, registry)).toArray(Slice[]::new) :
                null;
        } else {
            this.labels = null;
            this.labelCount = 0;
            this.labeledInstanceExpirationEnabled = false;
            this.labeledInstanceAutoRemovalEnabled = false;
            this.minLabeledInstanceExpirationTimeMs = 0L;
            this.exclusionPredicate = null;
            SC allSliceConfig = config.allSliceConfig();

            if (allSliceConfig.isEnabled() && allSliceConfig.isTotalEnabled()) {
                SliceContext sliceContext = new SliceContext<>(
                    name,
                    config,
                    removed,
                    listeners,
                    null,
                    labelCount,
                    measurableValueProvidersProvider,
                    meterImplMaker,
                    instanceMaker,
                    meterImplUpdater,
                    timeMsProvider,
                    executor);

                this.allSlice = new Slice<>(sliceContext, config.allSliceConfig(), registry);
            } else {
                allSlice = null;
            }

            this.slices = null;
        }

        this.executor = executor;
    }

    private static long minLabeledInstanceExpirationTimeMs(
        MeterSliceConfig allSliceConfig,
        Set> enabledSliceConfigs) {

        Optional slicesResult = enabledSliceConfigs.stream()
            .filter(MeterSliceConfig::isLabeledInstanceExpirationEnabled)
            .map(sc -> sc.labeledInstanceExpirationTime().toMillis())
            .min(Long::compareTo);

        if (allSliceConfig.isEnabled() && allSliceConfig.isLabeledInstanceExpirationEnabled()) {
            long allSliceResult = allSliceConfig.labeledInstanceExpirationTime().toMillis();
            return min(allSliceResult, slicesResult.orElse(allSliceResult));
        } else {
            return slicesResult.orElseThrow();
        }
    }

    @Override
    @SuppressWarnings("DuplicatedCode")
    public void addListener(MetricListener listener) {
        if (!isEnabled() || isRemoved()) {
            return;
        }

        executor.execute(() -> {
            if (isRemoved()) {
                return;
            }

            listeners.add(listener);

            forEach(instance -> {
                notifyListener(listener, l -> l.metricInstanceAdded(instance));
                instance.metricInstanceAdded();
            });
        });
    }

    protected boolean isRemoved() {
        return removed.get();
    }

    @Override
    public void metricAdded() {
        if (labeledInstanceExpirationEnabled) {
            executor.schedule(
                this::removeExpiredInstancesAndSchedule,
                minLabeledInstanceExpirationTimeMs + EXPIRED_INSTANCES_REMOVAL_ADDITIONAL_DELAY_MS, MILLISECONDS);
        }
    }

    @SuppressWarnings("ForLoopReplaceableByForEach")
    private void removeExpiredInstancesAndSchedule() {
        if (isRemoved()) {
            return;
        }

        removeExpiredInstances(true);
        long baseDelayMs = minLabeledInstanceExpirationTimeMs;
        long nowMs = timeMsProvider.timeMs();

        if (allSlice != null
            && allSlice.labeledInstanceExpirationEnabled
            && allSlice.instanceExpirationManager.hasInstanceExpirations()) {

            baseDelayMs = min(baseDelayMs, max(allSlice.instanceExpirationManager.minInstanceExpirationTimeMs - nowMs, 0L));
        }

        if (slices != null) {
            for (int i = 0; i < slices.length; ++i) {
                Slice slice = slices[i];

                if (slice.labeledInstanceExpirationEnabled && slice.instanceExpirationManager.hasInstanceExpirations()) {
                    baseDelayMs = min(baseDelayMs, max(slice.instanceExpirationManager.minInstanceExpirationTimeMs - nowMs, 0L));
                }
            }
        }

        executor.schedule(
            this::removeExpiredInstancesAndSchedule,
            baseDelayMs + EXPIRED_INSTANCES_REMOVAL_ADDITIONAL_DELAY_MS, MILLISECONDS);
    }

    public void metricRemoved() {
        if (isRemoved()) {
            return;
        }

        if (!isEnabled()) {
            removed.set(true);
            return;
        }

        executor.execute(() -> {
            if (isRemoved()) {
                return;
            }

            removed.set(true);

            forEach(instance -> {
                listeners.forEach(listener -> listener.metricInstanceRemoved(instance));
                instance.metricInstanceRemoved();
            });

            listeners.clear();
        });
    }

    @Override
    public Iterator iterator() {
        removeExpiredInstances(false);
        return isEnabled() ? new InstancesIterator(allSlice, slices) : emptyIterator();
    }

    @SuppressWarnings("ForLoopReplaceableByForEach")
    private void removeExpiredInstances(boolean inExecutorThread) {
        if (!labeledInstanceExpirationEnabled || isRemoved()) {
            return;
        }

        long nowMs = timeMsProvider.timeMs();

        if (allSlice != null && allSlice.labeledInstanceExpirationEnabled) {
            allSlice.instanceExpirationManager.wake(false, nowMs, inExecutorThread);
        }

        if (slices != null) {
            for (int i = 0; i < slices.length; ++i) {
                Slice slice = slices[i];

                if (slice.labeledInstanceExpirationEnabled) {
                    slice.instanceExpirationManager.wake(false, nowMs, inExecutorThread);
                }
            }
        }
    }

    protected void update(long value, LabelValues labelValues) {
        if (!isEnabled() || isRemoved()) {
            return;
        }

        List valueList = labelValues.list();
        checkLabelValues(valueList);

        if (areExcluded(labelValues)) {
            return;
        }

        long updateTimeMs = labeledInstanceAutoRemovalEnabled ? timeMsProvider.timeMs() : 0L;

        if (allSlice != null) {
            allSlice.update(value, valueList, updateTimeMs);
        }

        if (slices != null) {
            for (Slice slice : slices) {
                if (slice.matches(labelValues)) {
                    slice.update(value, valueList, updateTimeMs);
                }
            }
        }
    }

    private void checkLabelValues(List labelValues) {
        if (labels != null) {
            if (labelValues == null || labelValues.size() != labels.length) {
                unexpected(labelValues);
            }

            for (int i = 0; i < labelCount; ++i) {
                if (!labels[i].equals(labelValues.get(i).label())) {
                    unexpected(labelValues);
                }
            }
        } else if (labelValues != null && !labelValues.isEmpty()) {
            unexpected(labelValues);
        }
    }

    private void unexpected(List labelValues) {
        throw new IllegalArgumentException(
            "labelValues = " + labelValues +
            " do not match labels = " + Arrays.toString(labels));
    }

    private boolean areExcluded(LabelValues labelValues) {
        return exclusionPredicate != null && exclusionPredicate.matches(labelValues);
    }

    @Override
    public void removeInstancesFor(LabelValues labelValues) {
        if (!isEnabled() || isRemoved()) {
            return;
        }

        List valueList = labelValues.list();
        checkLabelValues(valueList);

        if (areExcluded(labelValues)) {
            return;
        }

        if (allSlice != null) {
            allSlice.removeInstancesFor(labelValues);
        }

        if (slices != null) {
            for (Slice slice : slices) {
                if (slice.matches(labelValues)) {
                    slice.removeInstancesFor(labelValues);
                }
            }
        }
    }

    public static abstract class AbstractMeterInstance implements MeterInstance {

        private final MetricName name;
        private final List labelValues;
        private final Map labelToValue;
        private final boolean totalInstance;
        private final boolean labeledMetricTotalInstance;
        private final boolean levelInstance;
        private final MeasurableValuesProvider measurableValuesProvider;
        private final Map> measurableValueProviders;
        private final Set measurables;
        private final boolean withPercentiles;
        private final boolean withBuckets;
        private final MI meterImpl;

        protected AbstractMeterInstance(
            MetricName name,
            List labelValues,
            boolean totalInstance,
            boolean labeledMetricTotalInstance,
            boolean levelInstance,
            MeasurableValuesProvider measurableValuesProvider,
            Map> measurableValueProviders,
            MI meterImpl) {

            this.name = name;
            this.labelValues = labelValues;

            this.labelToValue =
                !labelValues.isEmpty() ?
                labelValues.stream().collect(toMap(LabelValue::label, lv -> lv)) :
                emptyMap();

            this.totalInstance = totalInstance;
            this.labeledMetricTotalInstance = labeledMetricTotalInstance;
            this.levelInstance = levelInstance;
            this.measurableValuesProvider = measurableValuesProvider;
            this.measurableValueProviders = measurableValueProviders;
            this.measurables = measurableValueProviders.keySet();
            this.withPercentiles = measurables().stream().anyMatch(m -> m instanceof Histogram.Percentile);
            this.withBuckets = measurables().stream().anyMatch(m -> m instanceof Histogram.Bucket);
            this.meterImpl = meterImpl;
        }

        @Override
        public MetricName name() {
            return name;
        }

        @Override
        public List labelValues() {
            return labelValues;
        }

        @Override
        public Map labelToValue() {
            return labelToValue;
        }

        @Override
        public boolean isTotalInstance() {
            return totalInstance;
        }

        @Override
        public boolean isLabeledMetricTotalInstance() {
            return labeledMetricTotalInstance;
        }

        @Override
        public boolean isLevelInstance() {
            return levelInstance;
        }

        @Override
        public Set measurables() {
            return measurables;
        }

        @Override
        public boolean isWithPercentiles() {
            return withPercentiles;
        }

        @Override
        public boolean isWithBuckets() {
            return withBuckets;
        }

        protected MI meterImpl() {
            return meterImpl;
        }

        @Override
        @SuppressWarnings("unchecked")
        public  V valueOf(Measurable measurable) throws NotMeasuredException {
            if (measurables.contains(measurable)) {
                return (V)measurableValueProviders.get(measurable).valueFor(meterImpl);
            } else {
                throw NotMeasuredException.forMeasurable(measurable);
            }
        }

        @Override
        public MeasurableValues measurableValues() {
            return measurableValuesProvider.measurableValues();
        }

        protected void update(long value, MeterImplUpdater meterImplUpdater) {
            meterImplUpdater.update(meterImpl, value);
        }
    }

    public static abstract class AbstractExpirableMeterInstance extends AbstractMeterInstance {

        private volatile long updateTimeMs;

        protected AbstractExpirableMeterInstance(
            MetricName name,
            List labelValues,
            boolean totalInstance,
            boolean labeledMetricTotalInstance,
            boolean levelInstance,
            MeasurableValuesProvider measurableValuesProvider,
            Map> measurableValueProviders,
            MI meterImpl,
            long creationTimeMs) {

            super(
                name,
                labelValues,
                totalInstance,
                labeledMetricTotalInstance,
                levelInstance,
                measurableValuesProvider,
                measurableValueProviders,
                meterImpl);

            this.updateTimeMs = creationTimeMs;
        }

        protected void update(
            long value,
            MeterImplUpdater meterImplUpdater,
            long newUpdateTimeMs) {

            update(value, meterImplUpdater);
            long oldUpdateTimeMs = updateTimeMs;
            updateTimeMs = max(newUpdateTimeMs, oldUpdateTimeMs);
        }

        private long updateTimeMs() {
            return updateTimeMs;
        }
    }

    private static class InstanceKey {

        final List labelValues;
        final Label[] labelsMask;
        final int hashCode;

        InstanceKey(List labelValues, Label[] labelsMask) {
            this.labelValues = labelValues;
            this.labelsMask = labelsMask;
            this.hashCode = hashCodeFor(labelValues, labelsMask);
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }

            if (other == null || getClass() != other.getClass()) {
                return false;
            }

            InstanceKey that = (InstanceKey)other;

            if (hashCode != that.hashCode) {
                return false;
            }

            if (labelsMask != that.labelsMask) {
                return false;
            }

            for (int i = 0; i < labelsMask.length; ++i) {
                if (labelsMask[i] != null
                    && !labelValues.get(i).value().equals(that.labelValues.get(i).value())) {

                    return false;
                }
            }

            return true;
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

        static int hashCodeFor(List labelValues, Label[] labelsMask) {
            HashCodeBuilder builder = new HashCodeBuilder(17, 37);

            for (int i = 0; i < labelsMask.length; ++i) {
                if (labelsMask[i] != null) {
                    builder.append(labelValues.get(i));
                }
            }

            return builder.toHashCode();
        }
    }

    private static class SliceContext<
        MI,
        IC extends MeterInstanceConfig,
        SC extends MeterSliceConfig,
        C extends MeterConfig> {

        final MetricName parentName;
        final C parentConfig;
        final AtomicBoolean parentRemoved;
        final List listeners;
        final Label[] parentLabels;
        final int parentLabelCount;
        final MeasurableValueProvidersProvider measurableValueProvidersProvider;
        final MeterImplMaker meterImplMaker;
        final InstanceMaker instanceMaker;
        final MeterImplUpdater meterImplUpdater;
        final TimeMsProvider timeMsProvider;
        final ScheduledExecutorService executor;

        SliceContext(
            MetricName parentName,
            C parentConfig,
            AtomicBoolean parentRemoved,
            List listeners,
            Label[] parentLabels,
            int parentLabelCount,
            MeasurableValueProvidersProvider measurableValueProvidersProvider,
            MeterImplMaker meterImplMaker,
            InstanceMaker instanceMaker,
            MeterImplUpdater meterImplUpdater,
            TimeMsProvider timeMsProvider,
            ScheduledExecutorService executor) {

            this.parentName = parentName;
            this.parentConfig = parentConfig;
            this.parentRemoved = parentRemoved;
            this.listeners = listeners;
            this.parentLabels = parentLabels;
            this.parentLabelCount = parentLabelCount;
            this.measurableValueProvidersProvider = measurableValueProvidersProvider;
            this.meterImplMaker = meterImplMaker;
            this.instanceMaker = instanceMaker;
            this.meterImplUpdater = meterImplUpdater;
            this.timeMsProvider = timeMsProvider;
            this.executor = executor;
        }

        boolean hasPrefixLabelValues() {
            return parentConfig.hasPrefixLabelValues();
        }

        boolean isParentRemoved() {
            return parentRemoved.get();
        }

        void forEachListener(Consumer action) {
            listeners.forEach(l -> notifyListener(l, action));
        }

        Label parentLabel(int i) {
            return parentLabels[i];
        }

        void execute(Runnable task) {
            try {
                executor.execute(task);
            } catch (Exception e) {
                logger.error("Failed to execute task", e);
                throw e;
            }
        }
    }

    private static class Slice<
        MI,
        IC extends MeterInstanceConfig,
        SC extends MeterSliceConfig,
        C extends MeterConfig> implements Iterable {

        static final long INFINITE_LABELED_INSTANCE_EXPIRATION_TIME_MS = DAYS.toMillis(10000L);

        final MetricRegistry registry;
        final SliceContext context;
        final int parentLabelCount;
        final SC config;
        final MetricName name;

        final LabelValuesPredicate predicate;
        final List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy