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

com.netflix.concurrency.limits.strategy.PredicatePartitionStrategy Maven / Gradle / Ivy

package com.netflix.concurrency.limits.strategy;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import com.netflix.concurrency.limits.MetricIds;
import com.netflix.concurrency.limits.MetricRegistry;
import com.netflix.concurrency.limits.MetricRegistry.SampleListener;
import com.netflix.concurrency.limits.Strategy;
import com.netflix.concurrency.limits.internal.EmptyMetricRegistry;
import com.netflix.concurrency.limits.internal.Preconditions;

/**
 * Concurrency limiter that guarantees a certain percentage of the limit to specific callers
 * while allowing callers to borrow from underutilized callers.
 * 
 * Callers are identified by their index into an array of percentages passed in during initialization.
 * A percentage of 0.0 means that a caller may only use excess capacity and can be completely 
 * starved when the limit is reached.  A percentage of 1.0 means that the caller
 * is guaranteed to get the entire limit.
 * 
 * grpc.server.call.inflight (group=[a, b, c])
 * grpc.server.call.limit (group=[a,b,c])
 */
public final class PredicatePartitionStrategy implements Strategy {
    private static final String PARTITION_TAG_NAME = "partition";
    
    public static class Builder {
        private final List> partitions = new ArrayList<>();
        private MetricRegistry registry = EmptyMetricRegistry.INSTANCE;
        
        public Builder metricRegistry(MetricRegistry registry) {
            this.registry = registry;
            return this;
        }
        
        public Builder add(String name, Double pct, Predicate predicate) {
            partitions.add(new Partition(name, pct, predicate));
            return this;
        }
        
        public PredicatePartitionStrategy build() {
            return new PredicatePartitionStrategy(this);
        }

        public boolean hasPartitions() {
            return !partitions.isEmpty();
        }
    }
    
    public static  Builder newBuilder() {
        return new Builder();
    }

    private final List> partitions;
    private int busy = 0;
    private int limit = 0;
    
    private PredicatePartitionStrategy(Builder builder) {
        Preconditions.checkArgument(builder.partitions.stream().map(Partition::getPercent).reduce(0.0, Double::sum) <= 1.0, 
                "Sum of percentages must be <= 1.0");

        this.partitions = new ArrayList<>(builder.partitions);
        this.partitions.forEach(partition -> partition.createMetrics(builder.registry));
        
        builder.registry.registerGauge(MetricIds.LIMIT_GUAGE_NAME, this::getLimit);
    }
    
    @Override
    public synchronized Token tryAcquire(T type) {
        for (final Partition partition : partitions) {
            if (partition.predicate.test(type)) {
                if (busy >= limit && partition.isLimitExceeded()) {
                    break;
                }
                busy++;
                partition.acquire();
                return Token.newAcquired(busy, () -> releasePartition(partition));
            }
        }
        return Token.newNotAcquired(busy);
    }
    
    private synchronized void releasePartition(Partition partition) {
        busy--;
        partition.release();
    }

    @Override
    public synchronized void setLimit(int newLimit) { 
        if (this.limit != newLimit) {
            this.limit = newLimit;
            partitions.forEach(partition -> partition.updateLimit(newLimit));
        }
    }
    
    public synchronized int getLimit() {
        return limit;
    }

    private static class Partition {
        private final double percent;
        private final Predicate predicate;
        private final String name;
        private SampleListener inflightDistribution;
        private int limit;
        private int busy;
        
        public Partition(String name, double pct, Predicate predicate) {
            this.name = name;
            this.percent = pct;
            this.predicate = predicate;
        }
        
        public void createMetrics(MetricRegistry registry) {
            this.inflightDistribution = registry.registerDistribution(MetricIds.INFLIGHT_GUAGE_NAME, PARTITION_TAG_NAME, name);
            registry.registerGauge(MetricIds.PARTITION_LIMIT_GUAGE_NAME, this::getLimit, PARTITION_TAG_NAME, name);
        }
        
        public void updateLimit(int totalLimit) {
            // Calculate this bin's limit while rounding up and ensuring the value
            // is at least 1.  With this technique the sum of bin limits may end up being 
            // higher than the concurrency limit.
            this.limit = (int)Math.max(1, Math.ceil(totalLimit * percent));
        }

        public boolean isLimitExceeded() {
            return busy >= limit;
        }

        public void acquire() {
            busy++;
            inflightDistribution.addSample(busy);
        }
        
        public void release() {
            busy--;
        }

        public int getLimit() {
            return limit;
        }
        
        public double getPercent() {
            return percent;
        }
        
        @Override
        public String toString() {
            return "Partition [pct=" + percent + ", limit=" + limit + ", busy=" + busy + "]";
        }
    }

    synchronized int getBinBusyCount(int index) {
        Preconditions.checkArgument(index >= 0 && index < partitions.size(), "Invalid bin index " + index);
        return partitions.get(index).busy;
    }

    synchronized int getBinLimit(int index) {
        Preconditions.checkArgument(index >= 0 && index < partitions.size(), "Invalid bin index " + index);
        return partitions.get(index).limit;
    }
    
    synchronized public int getBusyCount() {
        return busy;
    }
    
    @Override
    public String toString() {
        final int maxLen = 10;
        return "PercentageStrategy [" + partitions.subList(0, Math.min(partitions.size(), maxLen)) + "]";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy