/** * This file is part of Waarp Project. * * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the * COPYRIGHT.txt in the distribution for a full listing of individual contributors. * * All Waarp Project is free software: you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Waarp . If not, see * <http://www.gnu.org/licenses/>. */ package org.waarp.common.cpu; import java.util.LinkedList; import java.util.Random; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import io.netty.handler.traffic.AbstractTrafficShapingHandler; import org.waarp.common.database.DbAdmin; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; /** * Abstract class for Constraint Limit Handler for Waarp project * * @author Frederic Bregier * */ public abstract class WaarpConstraintLimitHandler implements Runnable { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(WaarpConstraintLimitHandler.class); private static final String NOALERT = "noAlert"; public static final long LOWBANDWIDTH_DEFAULT = 1048576; public String lastAlert = NOALERT; private boolean constraintInactive = true; private boolean useCpuLimits = false; private boolean useBandwidthLimit = false; private final Random random = new Random(); private CpuManagementInterface cpuManagement; private double cpuLimit = 1.0; // was 0.8; private int channelLimit = 0; // was 1000; private boolean isServer = false; private double lastLA = 0.0; private long lastTime; // Dynamic throttling private long WAITFORNETOP = 1000; private long TIMEOUTCON = 10000; private double highCpuLimit = 0.0; // was 0.8; private double lowCpuLimit = 0.0; // was 0.5; private double percentageDecreaseRatio = 0.25; private long delay = 1000; private long limitLowBandwidth = LOWBANDWIDTH_DEFAULT; private AbstractTrafficShapingHandler handler; private ScheduledThreadPoolExecutor executor = null; private static class CurLimits { long read; long write; private CurLimits(long read, long write) { this.read = read; this.write = write; } } private final LinkedList<CurLimits> curLimits = new LinkedList<WaarpConstraintLimitHandler.CurLimits>(); private int nbSinceLastDecrease = 0; private static final int payload = 5; // 5 seconds of payload when new high cpu /** * Empty constructor */ public WaarpConstraintLimitHandler() { // Do nothing except setup standard value for inactivity if (cpuManagement == null) cpuManagement = new CpuManagementNoInfo(); } /** * This constructor enables only throttling bandwidth with cpu usage * * * @param WAITFORNETOP2 * 1000 ms as wait for a network operation * @param TIMEOUTCON2 * 10000 ms as timeout limit * @param useJdkCpuLimit * True to use JDK Cpu native or False for JavaSysMon * @param lowcpuLimit * for proactive cpu limitation (throttling bandwidth) (0<= x < 1 & highcpulimit) * @param highcpuLimit * for proactive cpu limitation (throttling bandwidth) (0<= x <= 1) 0 meaning no * throttle activated * @param percentageDecrease * for proactive cpu limitation, throttling bandwidth reduction (0 < x < 1) as 0.25 * for 25% of reduction * @param handler * the GlobalTrafficShapingHandler associated (null to have no proactive cpu * limitation) * @param delay * the delay between 2 tests for proactive cpu limitation * @param limitLowBandwidth * the minimal bandwidth (read or write) to apply when decreasing bandwidth (low * limit = 4096) */ public WaarpConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, boolean useJdkCpuLimit, double lowcpuLimit, double highcpuLimit, double percentageDecrease, AbstractTrafficShapingHandler handler, long delay, long limitLowBandwidth) { this(WAITFORNETOP2, TIMEOUTCON2, true, useJdkCpuLimit, 0, 0, lowcpuLimit, highcpuLimit, percentageDecrease, handler, delay, limitLowBandwidth); } /** * This constructor enables only Connection check ability * * @param useCpuLimit * True to enable cpuLimit on connection check * @param useJdKCpuLimit * True to use JDK Cpu native or False for JavaSysMon * @param cpulimit * high cpu limit (0<= x < 1) to refuse new connections * @param channellimit * number of connection limit (0<= x) */ public WaarpConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, boolean useCpuLimit, boolean useJdKCpuLimit, double cpulimit, int channellimit) { this(WAITFORNETOP2, TIMEOUTCON2, useCpuLimit, useJdKCpuLimit, cpulimit, channellimit, 0, 0, 0.01, null, 1000000, LOWBANDWIDTH_DEFAULT); } /** * This constructor enables both Connection check ability and throttling bandwidth with cpu * usage * * @param WAITFORNETOP2 * 1000 ms as wait for a network operation * @param TIMEOUTCON2 * 10000 ms as timeout limit * @param useCpuLimit * True to enable cpuLimit on connection check * @param useJdKCpuLimit * True to use JDK Cpu native or False for JavaSysMon * @param cpulimit * high cpu limit (0<= x < 1) to refuse new connections * @param channellimit * number of connection limit (0<= x) * @param lowcpuLimit * for proactive cpu limitation (throttling bandwidth) (0<= x < 1 & highcpulimit) * @param highcpuLimit * for proactive cpu limitation (throttling bandwidth) (0<= x <= 1) 0 meaning no * throttle activated * @param percentageDecrease * for proactive cpu limitation, throttling bandwidth reduction (0 < x < 1) as 0.25 * for 25% of reduction * @param handler * the GlobalTrafficShapingHandler associated (null to have no proactive cpu * limitation) * @param delay * the delay between 2 tests for proactive cpu limitation * @param limitLowBandwidth * the minimal bandwidth (read or write) to apply when decreasing bandwidth (low * limit = 4096) */ public WaarpConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, boolean useCpuLimit, boolean useJdKCpuLimit, double cpulimit, int channellimit, double lowcpuLimit, double highcpuLimit, double percentageDecrease, AbstractTrafficShapingHandler handler, long delay, long limitLowBandwidth) { useCpuLimits = useCpuLimit; WAITFORNETOP = WAITFORNETOP2; TIMEOUTCON = TIMEOUTCON2; lowCpuLimit = lowcpuLimit; highCpuLimit = highcpuLimit; this.limitLowBandwidth = limitLowBandwidth; if (this.limitLowBandwidth < LOWBANDWIDTH_DEFAULT) { this.limitLowBandwidth = LOWBANDWIDTH_DEFAULT; } this.delay = delay; if (lowCpuLimit <= 0) { lowCpuLimit = highCpuLimit / 2; } percentageDecreaseRatio = percentageDecrease; if (percentageDecreaseRatio <= 0) { percentageDecreaseRatio = 0.01; } else if (percentageDecreaseRatio >= 1) { percentageDecreaseRatio /= 100; } if (delay < (WAITFORNETOP >> 1)) { this.delay = WAITFORNETOP; } this.handler = handler; if (useCpuLimits || highCpuLimit > 0) { if (useJdKCpuLimit) { try { cpuManagement = new CpuManagement(); constraintInactive = false; } catch (UnsupportedOperationException e) { cpuManagement = new CpuManagementNoInfo(); constraintInactive = true; } } else { cpuManagement = new CpuManagementSysmon(); constraintInactive = false; } } else { // no test at all constraintInactive = true; cpuManagement = new CpuManagementNoInfo(); } useBandwidthLimit = highcpuLimit > 0; cpuLimit = cpulimit; channelLimit = channellimit; lastTime = System.currentTimeMillis(); if (this.handler != null && (!constraintInactive) && (!useBandwidthLimit)) { executor = new ScheduledThreadPoolExecutor(1); executor.scheduleWithFixedDelay(this, this.delay, this.delay, TimeUnit.MILLISECONDS); } } /** * Release the resources */ public void release() { if (this.executor != null) { this.executor.shutdownNow(); } } /** * To explicitly set this handler as server mode * * @param isServer */ public void setServer(boolean isServer) { this.isServer = isServer; } private double getLastLA() { long newTime = System.currentTimeMillis(); // first check if last test was done too shortly if ((newTime - lastTime) < (WAITFORNETOP >> 1)) { // If last test was wrong, then redo the test if (lastLA <= cpuLimit) { // last test was OK, so Continue return lastLA; } } lastTime = newTime; lastLA = cpuManagement.getLoadAverage(); return lastLA; } /** * * @return True if one of the limit is exceeded. Always False if not a server mode */ public boolean checkConstraints() { if (!isServer) return false; if ((useCpuLimits) && cpuLimit < 1 && cpuLimit > 0) { getLastLA(); if (lastLA <= cpuLimit) { lastAlert = NOALERT; return false; } if (lastLA > cpuLimit) { lastAlert = "CPU Constraint: " + lastLA + " > " + cpuLimit; logger.debug(lastAlert); return true; } } if (channelLimit > 0) { int nb = DbAdmin.getNbConnection() - DbAdmin.getHttpSession(); if (channelLimit < nb) { lastAlert = "Network Constraint: " + nb + " > " + channelLimit; logger.debug(lastAlert); return true; } nb = getNumberLocalChannel(); if (channelLimit < nb) { lastAlert = "LocalNetwork Constraint: " + nb + " > " + channelLimit; logger.debug(lastAlert); return true; } } lastAlert = NOALERT; return false; } /** * * @return the current number of active Local Channel */ protected abstract int getNumberLocalChannel(); /** * Same as checkConstraints except that the thread will sleep some time proportionally to the * current Load (if CPU related) * * @param step * the current step in retry * @return True if one of the limit is exceeded. Always False if not a server mode */ public boolean checkConstraintsSleep(int step) { if (!isServer) return false; long delay = WAITFORNETOP >> 1; if ((useCpuLimits) && cpuLimit < 1 && cpuLimit > 0) { long newTime = System.currentTimeMillis(); // first check if last test was done too shortly if ((newTime - lastTime) < delay) { // If last test was wrong, then wait a bit then redo the test if (lastLA > cpuLimit) { double sleep = lastLA * delay * (step + 1) * random.nextFloat(); long shorttime = (((long) sleep) / 10) * 10; if (shorttime >= 10) { try { Thread.sleep(shorttime); } catch (InterruptedException e) { } } } else { // last test was OK, so Continue lastAlert = NOALERT; return false; } } } if (checkConstraints()) { delay = getSleepTime() * (step + 1); try { Thread.sleep(delay); } catch (InterruptedException e) { } return true; } else { lastAlert = NOALERT; return false; } } /** * * @return a time below TIMEOUTCON with a random */ public long getSleepTime() { return (((long) (TIMEOUTCON * random.nextFloat()) + 5000) / 10) * 10; } /** * @return the cpuLimit */ public double getCpuLimit() { return cpuLimit; } /** * @param cpuLimit * the cpuLimit to set */ public void setCpuLimit(double cpuLimit) { this.cpuLimit = cpuLimit; } /** * @return the channelLimit */ public int getChannelLimit() { return channelLimit; } /** * @param channelLimit * the channelLimit to set */ public void setChannelLimit(int channelLimit) { this.channelLimit = channelLimit; } /** * Get the current setting on Read Limit (supposed to be not the value in the handler but in the * configuration) * * @return the current setting on Read Limit */ protected abstract long getReadLimit(); /** * Get the current setting on Write Limit (supposed to be not the value in the handler but in * the configuration) * * @return the current setting on Write Limit */ protected abstract long getWriteLimit(); /** * Set the handler * * @param handler */ public void setHandler(AbstractTrafficShapingHandler handler) { this.handler = handler; if ((!constraintInactive) && this.handler != null && useBandwidthLimit) { if (executor != null) { executor.shutdownNow(); } logger.debug("Activate Throttle bandwidth according to CPU usage"); executor = new ScheduledThreadPoolExecutor(1); executor.scheduleWithFixedDelay(this, this.delay, this.delay, TimeUnit.MILLISECONDS); } else { if (executor != null) { executor.shutdownNow(); executor = null; } } } /** * Check every delay if the current cpu usage needs to relax or to constraint the bandwidth */ public void run() { if (constraintInactive) return; double curLA = getLastLA(); if (!useBandwidthLimit) return; if (curLA > highCpuLimit) { CurLimits curlimit = null; if (curLimits.isEmpty()) { // get current limit setting curlimit = new CurLimits(getReadLimit(), getWriteLimit()); if (curlimit.read == 0) { // take the current bandwidth curlimit.read = handler.trafficCounter().lastReadThroughput(); if (curlimit.read < limitLowBandwidth) { curlimit.read = 0; } } if (curlimit.write == 0) { // take the current bandwidth curlimit.write = handler.trafficCounter().lastWriteThroughput(); if (curlimit.write < limitLowBandwidth) { curlimit.write = 0; } } } else { curlimit = curLimits.getLast(); } long newread = (long) (curlimit.read * (1 - percentageDecreaseRatio)); if (newread < limitLowBandwidth) { newread = limitLowBandwidth; } long newwrite = (long) (curlimit.write * (1 - percentageDecreaseRatio)); if (newwrite < limitLowBandwidth) { newwrite = limitLowBandwidth; } CurLimits newlimit = new CurLimits(newread, newwrite); if (curLimits.isEmpty() || curlimit.read != newread || curlimit.write != newwrite) { // Not same limit so add this limit curLimits.add(newlimit); logger.debug("Set new low limit since CPU = " + curLA + " " + newwrite + ":" + newread); handler.configure(newlimit.write, newlimit.read); nbSinceLastDecrease += payload; } } else if (curLA < lowCpuLimit) { if (curLimits.isEmpty()) { // nothing to do return; } if (nbSinceLastDecrease > 0) { nbSinceLastDecrease--; // wait a bit more in case return; } nbSinceLastDecrease = 0; curLimits.pollLast(); CurLimits newlimit = null; if (curLimits.isEmpty()) { // reset to default limits long newread = getReadLimit(); long newwrite = getWriteLimit(); logger.debug("restore limit since CPU = " + curLA + " " + newwrite + ":" + newread); handler.configure(newwrite, newread); } else { // set next upper values newlimit = curLimits.getLast(); long newread = newlimit.read; long newwrite = newlimit.write; logger.debug("Set new upper limit since CPU = " + curLA + " " + newwrite + ":" + newread); handler.configure(newwrite, newread); // give extra payload to prevent a brutal return to normal nbSinceLastDecrease = payload; } } } }