package qa.qcri.aidr.common.redis; import org.apache.log4j.Logger; /** * A utility class to implement load shedding, i.e. skipping the processing of certain items if too * many items have been processed recently. * * The limitation is defined as the maximum number of items to be processed during a certain number * of minutes. * */ public class LoadShedder { /** * A common logger for all the load shedders. */ private static Logger logger = Logger.getLogger(LoadShedder.class); /** * The duration of the interval in minutes. */ final double intervalMinutes; /** * The duration of the interval in milliseconds (computed from {@link #intervalMinutes}). */ final long intervalMillis; /** * The maximum number of items that can be processed during an interval. */ final int maxLimit; /** * Whether to emit a warning when loading or not. */ final boolean warnOnLimit; /** * String identifying this load shedder for logging purposes, e.g. a channel name */ final String name; /** * The beginning of the interval. */ long lastSetTime = 0; /** * The number of items processed in this interval. */ int counter; /** * Whether a warning has been issued already for this interval. */ boolean warningEmitted; /** * Creates a load shedder that will allow the processing of up to maxLimit items in a period of * intervalMinutes. * * @param maxLimit maximum number of messages in an interval * @param intervalMinutes duration of the interval in minutes (int) * @param warnOnLimit if true, then log a warning message when rate limit is exceeded * @param name the name of this load shedder, used for logging purposes. */ public LoadShedder(final int maxLimit, final int intervalMinutes, final boolean warnOnLimit, String name) { this(maxLimit, (double)intervalMinutes, warnOnLimit, name); } /** * Creates a load shedder that will allow the processing of up to maxLimit items in a period of * intervalMinutes. * * @param intervalMinutes duration of the interval in minutes or fractions of minutes (double) * @param maxLimit maximum number of items in an interval * @param warnOnLimit if true, then log a warning message when rate limit is exceeded * @param name the name of this load shedder, used for logging purposes. */ public LoadShedder(final int maxLimit, final double intervalMinutes, final boolean warnOnLimit, String name) { this.maxLimit = maxLimit; this.intervalMinutes = intervalMinutes; this.intervalMillis = (long)(intervalMinutes * 1000.0 * 60.0); this.warnOnLimit = warnOnLimit; this.name = name; this.counter = 0; this.lastSetTime = 0; this.warningEmitted = false; logger.info("Loadshedder[" + this.name + "] initialized with " + this.maxLimit + " per " + this.intervalMinutes + "min of items"); } /** * Gets the number of messages processed so far in this interval. * * @return the number */ public int getCounter() { return counter; } /** * Gets the flag indicating whether we should log a warning if the rate is exceeded * * @return the flag */ public boolean getWarn() { return warnOnLimit; } /** * Gets the maximum number of items that can be processed during an interval * * @return the limit */ public int getMaxLimit() { return maxLimit; } /** * Gets the name of this load shedder, a String used for logging purposes * * @return the name */ public String getName() { return name; } /** * This method is called before processing an item. If the method returns true, the item should * be processed. If the method returns false, the item should not be processed, because we have * exceeded the number of items that can be processed during a certain period. * * @return true if the current item should be processed, false if the current item should not be * processed */ public boolean canProcess() { long currentTimeMillis = System.currentTimeMillis(); double deltaMillis = currentTimeMillis - lastSetTime; if ( (deltaMillis <= intervalMillis) || ( lastSetTime == 0 ) ) { counter++; if (counter <= maxLimit) { // Within limits lastSetTime = currentTimeMillis; return true; } else { // Outside limit, check if we need to emit a warning (only once per interval) if (warnOnLimit && !warningEmitted) { warningEmitted = true; logger.warn("LoadShedder[" + name + "] reached limit of " + maxLimit + " messages per " + intervalMinutes + " mins reached with current count = " + counter); } return false; } } else { // Interval over - now reset for next interval counter = 1; lastSetTime = currentTimeMillis; warningEmitted = false; return true; } } /** * Return the last set time for a process. * @return the lastSetTime */ public long getLastSetTime() { return lastSetTime; } /** * @param lastSetTime the lastSetTime to set */ public void setLastSetTime(long lastSetTime) { this.lastSetTime = lastSetTime; } }