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

com.wavefront.agent.handlers.TrafficShapingRateLimitAdjuster Maven / Gradle / Ivy

package com.wavefront.agent.handlers;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.RecyclableRateLimiter;
import com.wavefront.agent.data.EntityProperties;
import com.wavefront.agent.data.EntityPropertiesFactory;
import com.wavefront.common.EvictingRingBuffer;
import com.wavefront.common.Managed;
import com.wavefront.common.SynchronizedEvictingRingBuffer;
import com.wavefront.data.ReportableEntityType;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;

/**
 * Experimental: use automatic traffic shaping (set rate limiter based on recently received per
 * second rates, heavily biased towards last 5 minutes)
 *
 * @author [email protected].
 */
public class TrafficShapingRateLimitAdjuster extends TimerTask implements Managed {
  private static final Logger log =
      Logger.getLogger(TrafficShapingRateLimitAdjuster.class.getCanonicalName());
  private static final int MIN_RATE_LIMIT = 10; // 10 pps
  private static final double TOLERANCE_PERCENT = 5.0;

  private final Map entityPropsFactoryMap;
  private final double headroom;
  private final Map> perEntityStats =
      new EnumMap<>(ReportableEntityType.class);
  private final Timer timer;
  private final int windowSeconds;

  /**
   * @param entityPropsFactoryMap map of factory for entity properties factory (to control rate
   *     limiters)
   * @param windowSeconds size of the moving time window to average point rate
   * @param headroom headroom multiplier
   */
  public TrafficShapingRateLimitAdjuster(
      Map entityPropsFactoryMap,
      int windowSeconds,
      double headroom) {
    this.windowSeconds = windowSeconds;
    Preconditions.checkArgument(headroom >= 1.0, "headroom can't be less than 1!");
    Preconditions.checkArgument(windowSeconds > 0, "windowSeconds needs to be > 0!");
    this.entityPropsFactoryMap = entityPropsFactoryMap;
    this.headroom = headroom;
    this.timer = new Timer("traffic-shaping-adjuster-timer");
  }

  @Override
  public void run() {
    for (ReportableEntityType type : ReportableEntityType.values()) {
      for (EntityPropertiesFactory propsFactory : entityPropsFactoryMap.values()) {
        EntityProperties props = propsFactory.get(type);
        long rate = props.getTotalReceivedRate();
        EvictingRingBuffer stats =
            perEntityStats.computeIfAbsent(
                type, x -> new SynchronizedEvictingRingBuffer<>(windowSeconds));
        if (rate > 0 || stats.size() > 0) {
          stats.add(rate);
          if (stats.size() >= 60) { // need at least 1 minute worth of stats to enable the limiter
            RecyclableRateLimiter rateLimiter = props.getRateLimiter();
            adjustRateLimiter(type, stats, rateLimiter);
          }
        }
      }
    }
  }

  @Override
  public void start() {
    timer.scheduleAtFixedRate(this, 1000, 1000);
  }

  @Override
  public void stop() {
    timer.cancel();
  }

  @VisibleForTesting
  void adjustRateLimiter(
      ReportableEntityType type,
      EvictingRingBuffer sample,
      RecyclableRateLimiter rateLimiter) {
    List samples = sample.toList();
    double suggestedLimit =
        MIN_RATE_LIMIT
            + (samples.stream().mapToLong(i -> i).sum() / (double) samples.size()) * headroom;
    double currentRate = rateLimiter.getRate();
    if (Math.abs(currentRate - suggestedLimit) > currentRate * TOLERANCE_PERCENT / 100) {
      log.fine("Setting rate limit for " + type.toString() + " to " + suggestedLimit);
      rateLimiter.setRate(suggestedLimit);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy