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

org.directwebremoting.impl.ThrottlingServerLoadMonitor Maven / Gradle / Ivy

Go to download

DWR is easy Ajax for Java. It makes it simple to call Java code directly from Javascript. It gets rid of almost all the boiler plate code between the web browser and your Java code.

The newest version!
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.directwebremoting.impl;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.extend.WaitController;
import org.directwebremoting.util.HitMonitor;

/**
 * A smart implementation of ServerLoadMonitor.
 *
 * 

What a browser does:

*
 *       connected disconnected connected ...
 *      ____________          ____________
 *      |          |          |          |
 *      |          |          |          |
 * _____|          |__________|          |______
 *       [---cT---] [---dT---] [---cT---] ...
 * 
*

Where cT is the connectedTime and dT is the disconnectedTime.

* *

We impose some limits: a maximum number of simultaneously connected * browsers maxWaitingThreads, and the maximum number of * connections per second maxHitsPerSecond.

* *

We attempt to keep the actual waitingThreads and hitsPerSecond within * bounds by varying connectedTime and disconnectedTime.

* *

The system is in one of 3 modes: USAGE_LOW, USAGE_HIGH and USAGE_DIGG. The * boundary between USAGE_LOW and USAGE_HIGH is called threadOut. The boundary * between USAGE_HIGH and USAGE_DIGG is called hitOut.

* *

The system starts in USAGE_LOW mode. This mode uses constant values of * connectedTime=60 secs and disconnectedTime=0 secs. We could use much bigger * values for connectedTime (like infinite) however the servlet spec does not * enable servlet engines to inform us if the browser goes away so we check by * asking the browser to reconnect periodically.

* *

In USAGE_LOW mode we measure the number of clients using the number of * concurrently connected browsers (waitingThreads), when this goes above * maxWaitingThreads we move into USAGE_HIGH mode.

* *

On entering USAGE_HIGH mode, the settings (initially) change to * connectedTime=49 secs and disconnectedTime=1 sec. As the load increases the * connectedTime decreases linearly from 49 secs down to prevent the hits per * second from going above maxHitsPerSecond. If the connectedTime goes below * 1sec then the mode switches to USAGE_DIGG. If the connectedTime goes above * 49 secs then mode switches to USAGE_LOW.

* *

Note: there is some danger of an overlap where the system toggles between * USAGE_HIGH and USAGE_LOW. We need some way to prevent this from happening. *

* *

On entering USAGE_DIGG mode, the connectedTime changes to 0 secs, and the * disconnectedTime changes to 2 secs (to keep the round trip time at 2 secs). * The disconnectedTime alters to prevent the hitsPerSecond from going above * maxHitsPerSecond (In USAGE_HIGH mode the connectedTime was altered). * When the disconnectedTime would go under 2 secs, we switch back to USAGE_HIGH * mode.

* @author Joe Walker [joe at getahead dot org] */ public class ThrottlingServerLoadMonitor extends AbstractServerLoadMonitor { /* (non-Javadoc) * @see org.directwebremoting.extend.ServerLoadMonitor#supportsStreaming() */ public boolean supportsStreaming() { return true; } /* (non-Javadoc) * @see org.directwebremoting.extend.ServerLoadMonitor#getConnectedTime() */ public long getConnectedTime() { return connectedTime; } /* (non-Javadoc) * @see org.directwebremoting.ServerLoadMonitor#timeToNextPoll() */ public int getDisconnectedTime() { return disconnectedTime; } /* (non-Javadoc) * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitStarting(org.directwebremoting.extend.WaitController) */ @Override public void threadWaitStarting(WaitController controller) { super.threadWaitStarting(controller); hitMonitor.recordHit(); int currentWaitingThreads = waitingThreads.incrementAndGet(); // If we are already adjusting skip it. if (!adjusting.getAndSet(true)) { loadAdjust(currentWaitingThreads); adjusting.set(false); } } /* (non-Javadoc) * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitEnding(org.directwebremoting.extend.WaitController) */ @Override public void threadWaitEnding(WaitController controller) { super.threadWaitEnding(controller); waitingThreads.decrementAndGet(); } /** * Log a debug message if debug logging is enabled. * @param o */ private void debug(Object o) { if (log.isDebugEnabled()) { log.debug(o); } } /** * Check that we are setting the next poll time correctly. */ private void loadAdjust(int currentWaitingThreads) { // Adjust at most once every SECONDS_MONITORED / 2. if ((System.currentTimeMillis() - lastLoadAdjust.get()) < (secondsMonitored/2)) { return; } // USAGE_LOW check if (currentWaitingThreads < maxWaitingThreads) { // TODO: If the disconnectedTime was previously set different there is // no way to return to the default here. changeMode(USAGE_LOW, maxConnectedTime, disconnectedTime); return; } // Calculate the number of hits per second based on the number of hits since the // last time the monitor ran. float hitsPerSecond = (float) hitMonitor.getHitsInLastPeriod() / secondsMonitored; int hitsPerSecondAtThreadOut = maxWaitingThreads / roundTripAtThreadOutSeconds; int hitsPerSecondAtHitOut = maxHitsPerSecond; debug("Hits per second: " + hitsPerSecond); debug("Hits per second at ThreadOut: " + hitsPerSecondAtThreadOut); debug("Hits per second at HitOut: " + hitsPerSecondAtHitOut); // USAGE_HIGH if (hitsPerSecond < hitsPerSecondAtThreadOut) { // We should probably be in USAGE_LOW mode, so we force the low // end of the values in USAGE_HIGH mode changeMode(USAGE_HIGH, connectedTime, disconnectedTime); return; } float load = hitsPerSecond / maxHitsPerSecond; int loadBasedDisconnectedTime = (int) (disconnectedTime * load); if (mode.get() == USAGE_DIGG) { // If we're getting close to the upper bound then slow down changeMode(USAGE_DIGG, usageDiggConnectedTime, loadBasedDisconnectedTime); // Check that USAGE_DIGG is the correct mode and we shouldn't change if (disconnectedTime > usageDiggMinDisconnectedTime) { return; } // So we were in USAGE_DIGG, but disconnectedTime was so low that we // think USAGE_HIGH is a better mode to try } if (hitsPerSecond < hitsPerSecondAtHitOut) { // if hitsPerSecondAtThreadOut=0 and hitsPerSecondAtHitOut=1 // where would we score? float factor = (float) currentWaitingThreads / maxWaitingThreads; int tmpConnectedTime = (int) (connectedTime / factor); if (tmpConnectedTime > usageHighInitialConnectedTime) { tmpConnectedTime = usageHighInitialConnectedTime; } else if (tmpConnectedTime < usageHighFinalConnectedTime) { tmpConnectedTime = usageHighFinalConnectedTime; } changeMode(USAGE_HIGH, tmpConnectedTime, usageHighDisconnectedTime); return; } if (loadBasedDisconnectedTime < usageDiggMinDisconnectedTime) { changeMode(USAGE_DIGG, usageDiggConnectedTime, usageDiggMinDisconnectedTime); } else { changeMode(USAGE_DIGG, usageDiggConnectedTime, loadBasedDisconnectedTime); } } private void changeMode(int usageMode, int _connectedTime, int _disconnectedTime) { this.connectedTime = _connectedTime; this.disconnectedTime = _disconnectedTime; setMode(usageMode); } /** * For debug purposes we keep a track of what mode we are in. * @param newMode The new usage mode */ protected void setMode(int newMode) { int currentMode = this.mode.getAndSet(newMode); if (newMode != currentMode) { debug("Changing modes, from " + USAGE_NAMES[currentMode] + " to " + USAGE_NAMES[newMode]); } } protected int getMode() { return mode.get(); } /** * @param maxWaitingThreads the maxWaitingThreads to set */ public void setMaxWaitingThreads(int maxWaitingThreads) { this.maxWaitingThreads = maxWaitingThreads; } /** * @param maxHitsPerSecond the maxHitsPerSecond to set */ public void setMaxHitsPerSecond(int maxHitsPerSecond) { this.maxHitsPerSecond = maxHitsPerSecond; } /** * It might be good to expose this, however there are currently assumptions * in the code that the value is set to 60000. * See {@link #usageHighInitialConnectedTime}. * @param maxConnectedTime the maxConnectedTime to set */ void setMaxConnectedTime(int maxConnectedTime) { this.maxConnectedTime = maxConnectedTime; } /** * static calculations */ protected static final int usageHighDisconnectedTime = 1000; protected static final int usageHighInitialConnectedTime = 49000; protected static final int usageHighFinalConnectedTime = 1000; protected static final int usageDiggConnectedTime = 0; protected static final int usageDiggMinDisconnectedTime = usageHighDisconnectedTime + usageHighFinalConnectedTime; protected static final int hitOutRoundTripTime = usageHighDisconnectedTime + usageHighFinalConnectedTime; protected static final int threadOutRoundTripTime = usageHighInitialConnectedTime + usageHighDisconnectedTime; protected static final int roundTripAtThreadOutSeconds = threadOutRoundTripTime / 1000; /** * Static configuration data: The max number of threads we keep waiting. * We reduce the timeWithinPoll*Stream variables to reduce the load */ protected int maxWaitingThreads = 100; /** * Static configuration data: The max number of hits per second. * We increase the poll time to compensate and reduce the load. If this * number is not at least half maxWaitingThreads then the USAGE_HIGH mode * will not exist and the system will sublime from USAGE_LOW to USAGE_DIGG */ protected int maxHitsPerSecond = 100; /** * Static configuration data: What is the longest we wait for extra input * after detecting output. */ protected int maxConnectedTime = 60000; /** * The system is under-utilized. Everyone does comet. */ protected static final int USAGE_LOW = 0; /** * This system can't cope with everyone on comet, we are in mixed mode. */ protected static final int USAGE_HIGH = 1; /** * The system is very heavily used, polling only. */ protected static final int USAGE_DIGG = 2; /** * Some Strings to help us give some debug output */ protected static final String[] USAGE_NAMES = { "Low", "High", "Digg" }; /** * What is the current usage mode. */ protected AtomicInteger mode = new AtomicInteger(USAGE_LOW); /** * The time we are currently waiting before sending a browser away and * asking it to reconnect. */ protected int connectedTime = 60000; /** * How long are we telling users to wait before they come back next */ protected int disconnectedTime = 1000; /** * We are recording the number of hits in the last 10 seconds. */ protected int secondsMonitored = 10; /** * Our record of the server loading */ protected HitMonitor hitMonitor = new HitMonitor(secondsMonitored); /** * How many sleepers are there? */ protected AtomicInteger waitingThreads = new AtomicInteger(0); /** * Stores the last time the monitor made an adjustment and tells us if * the loadAdjustment is already taking place in another thread. */ protected AtomicLong lastLoadAdjust = new AtomicLong(System.currentTimeMillis()); protected AtomicBoolean adjusting = new AtomicBoolean(false); /** * The log stream */ private static final Log log = LogFactory.getLog(ThrottlingServerLoadMonitor.class); }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy