001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.impl;
017:
018: import org.apache.commons.logging.LogFactory;
019: import org.apache.commons.logging.Log;
020: import org.directwebremoting.extend.ServerLoadMonitor;
021: import org.directwebremoting.extend.WaitController;
022: import org.directwebremoting.util.HitMonitor;
023:
024: /**
025: * A smart implementation of ServerLoadMonitor.
026: *
027: * <p>What a browser does:</p>
028: * <pre>
029: * connected disconnected connected ...
030: * ____________ ____________
031: * | | | |
032: * | | | |
033: * _____| |__________| |______
034: * [---cT---] [---dT---] [---cT---] ...
035: * </pre>
036: * <p>Where cT is the connectedTime and dT is the disconnectedTime.</p>
037: *
038: * <p>We impose some limits: a maximum number of symultaneously connected
039: * browsers <code>maxWaitingThreads</code>, and the maximum number of
040: * connections per second <code>maxHitsPerSecond</code>.</p>
041: *
042: * <p>We attempt to keep the actual waitingThreads and hitsPerSecond within
043: * bounds by vairying connectedTime and disconnectedTime.</p>
044: *
045: * <p>The system is in one of 3 modes: USAGE_LOW, USAGE_HIGH and USAGE_DIGG. The
046: * boundary between USAGE_LOW and USAGE_HIGH is called threadOut. The boundary
047: * between USAGE_HIGH and USAGE_DIGG is called hitOut.</p>
048: *
049: * <p>The system starts in USAGE_LOW mode. This mode uses constant values of
050: * connectedTime=60 secs and disconnectedTime=0 secs. We could use much bigger
051: * values for connectedTime (like infinite) however the servlet spec does not
052: * enable servlet engines to inform us if the browser goes away so we check by
053: * asking the browser to reconnect periodically.</p>
054: *
055: * <p>In USAGE_LOW mode we measure the number of clients using the number of
056: * concurrently connected browsers (waitingThreads), when this goes above
057: * maxWaitingThreads we move into USAGE_HIGH mode.</p>
058: *
059: * <p>On entering USAGE_HIGH mode, the settings (initially) change to
060: * connectedTime=49 secs and disconnectedTime=1 sec. As the load increases the
061: * connectedTime decreases linearly from 49 secs down to prevent the hits per
062: * second from going above maxHitsPerSecond. If the connectedTime goes below
063: * 1sec then the mode switches to USAGE_DIGG. If the connectedTime goes above
064: * 49 secs then mode switches to USAGE_LOW.</p>
065: *
066: * <p>Note: there is some danger of an overlap where the system toggles between
067: * USAGE_HIGH and USAGE_LOW. We need some way to prevent this from happening.
068: * </p>
069: *
070: * <p>On entering USAGE_DIGG mode, the connectedTime changes to 0 secs, and the
071: * disconnectedTime changes to 2 secs (to keep the round trip time at 2 secs).
072: * The disconnectedTime alters to prevent the hitsPerSecond from going above
073: * maxHitsPerSecond (In USAGE_HIGH mode the connectedTime was altered).
074: * When the disconnectedTime would go under 2 secs, we switch back to USAGE_HIGH
075: * mode.</p>
076: * @author Joe Walker [joe at getahead dot org]
077: */
078: public class DefaultServerLoadMonitor extends AbstractServerLoadMonitor
079: implements ServerLoadMonitor {
080: /* (non-Javadoc)
081: * @see org.directwebremoting.extend.ServerLoadMonitor#supportsStreaming()
082: */
083: public boolean supportsStreaming() {
084: return true;
085: }
086:
087: /* (non-Javadoc)
088: * @see org.directwebremoting.extend.ServerLoadMonitor#getConnectedTime()
089: */
090: public long getConnectedTime() {
091: return connectedTime;
092: }
093:
094: /* (non-Javadoc)
095: * @see org.directwebremoting.ServerLoadMonitor#timeToNextPoll()
096: */
097: public int getDisconnectedTime() {
098: return disconnectedTime;
099: }
100:
101: /* (non-Javadoc)
102: * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitStarting(org.directwebremoting.extend.WaitController)
103: */
104: @Override
105: public void threadWaitStarting(WaitController controller) {
106: hitMonitor.recordHit();
107: waitingThreads++;
108: super .threadWaitStarting(controller);
109:
110: checkLoading();
111: }
112:
113: /* (non-Javadoc)
114: * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitEnding(org.directwebremoting.extend.WaitController)
115: */
116: @Override
117: public void threadWaitEnding(WaitController controller) {
118: waitingThreads--;
119: super .threadWaitEnding(controller);
120: }
121:
122: /**
123: * Check that we are setting the time to next poll correctly.
124: */
125: private void checkLoading() {
126: float hitsPerSecond = (float) hitMonitor.getHitsInLastPeriod()
127: / SECONDS_MONITORED;
128:
129: if (waitingThreads < maxWaitingThreads) {
130: connectedTime = maxConnectedTime;
131: disconnectedTime = 0;
132:
133: setMode(USAGE_LOW);
134: return;
135: }
136:
137: int roundTripAtThreadOutSeconds = threadOutRoundTripTime / 1000;
138:
139: int hitsPerSecondAtThreadOut = maxWaitingThreads
140: / roundTripAtThreadOutSeconds;
141: int hitsPerSecondAtHitOut = maxHitsPerSecond;
142:
143: if (hitsPerSecond < hitsPerSecondAtThreadOut) {
144: // We should probably be in USAGE_LOW mode, so we force the low
145: // end of the values in USAGE_HIGH mode
146: connectedTime = usageHighInitialConnectedTime;
147: disconnectedTime = usageHighDisconnectedTime;
148:
149: setMode(USAGE_HIGH);
150: return;
151: }
152:
153: if (mode == USAGE_DIGG) {
154: // If we're getting close to the upper bound then slow down
155: float load = hitsPerSecond / maxHitsPerSecond;
156: connectedTime = usageDiggConnectedTime;
157: disconnectedTime = (int) (disconnectedTime * load);
158:
159: // Check that USAGE_DIGG is the correct mode and we shouldn't change
160: if (disconnectedTime > usageDiggMinDisconnectedTime) {
161: setMode(USAGE_DIGG);
162: return;
163: }
164:
165: // So we were in USAGE_DIGG, but disconnectedTime was so low that we
166: // think USAGE_HIGH is a better mode to try
167: }
168:
169: if (hitsPerSecond < hitsPerSecondAtHitOut) {
170: // if hitsPerSecondAtThreadOut=0 and hitsPerSecondAtHitOut=1
171: // where would we score?
172: float factor = (float) waitingThreads / maxWaitingThreads;
173: connectedTime = (int) (connectedTime / factor);
174:
175: if (connectedTime > usageHighInitialConnectedTime) {
176: connectedTime = usageHighInitialConnectedTime;
177: }
178:
179: if (connectedTime < usageHighFinalConnectedTime) {
180: connectedTime = usageHighFinalConnectedTime;
181: }
182:
183: disconnectedTime = usageHighDisconnectedTime;
184:
185: setMode(USAGE_HIGH);
186: return;
187: }
188:
189: float load = hitsPerSecond / maxHitsPerSecond;
190: connectedTime = usageDiggConnectedTime;
191: disconnectedTime = (int) (disconnectedTime * load);
192:
193: if (disconnectedTime < usageDiggMinDisconnectedTime) {
194: disconnectedTime = usageDiggMinDisconnectedTime;
195: }
196:
197: setMode(USAGE_DIGG);
198: }
199:
200: /**
201: * For debug purposes we keep a track of what mode we are in.
202: * @param mode The new usage mode
203: */
204: protected void setMode(int mode) {
205: if (log.isDebugEnabled() && mode != this .mode) {
206: log.debug("Changing modes, from " + USAGE_NAMES[this .mode]
207: + " to " + USAGE_NAMES[mode]);
208: }
209:
210: this .mode = mode;
211: }
212:
213: /**
214: * @param maxWaitingThreads the maxWaitingThreads to set
215: */
216: public void setMaxWaitingThreads(int maxWaitingThreads) {
217: this .maxWaitingThreads = maxWaitingThreads;
218: }
219:
220: /**
221: * @param maxHitsPerSecond the maxHitsPerSecond to set
222: */
223: public void setMaxHitsPerSecond(int maxHitsPerSecond) {
224: this .maxHitsPerSecond = maxHitsPerSecond;
225: }
226:
227: /**
228: * It might be good top expose this, however there are currently assumptions
229: * in the code that the value is set to 60000.
230: * See {@link #usageHighInitialConnectedTime}.
231: * @param maxConnectedTime the maxConnectedTime to set
232: */
233: void setMaxConnectedTime(int maxConnectedTime) {
234: this .maxConnectedTime = maxConnectedTime;
235: }
236:
237: /**
238: *
239: */
240: protected static final int usageHighDisconnectedTime = 1000;
241: protected static final int usageHighInitialConnectedTime = 49000;
242: protected static final int usageHighFinalConnectedTime = 1000;
243: protected static final int usageDiggConnectedTime = 0;
244: protected static final int usageDiggMinDisconnectedTime = usageHighDisconnectedTime
245: + usageHighFinalConnectedTime;
246: protected static final int hitOutRoundTripTime = usageHighDisconnectedTime
247: + usageHighFinalConnectedTime;
248: protected static final int threadOutRoundTripTime = usageHighInitialConnectedTime
249: + usageHighDisconnectedTime;
250:
251: /**
252: * Static configuration data: The max number of threads we keep waiting.
253: * We reduce the timeWithinPoll*Stream variables to reduce the load
254: */
255: protected int maxWaitingThreads = 100;
256:
257: /**
258: * Static configuration data: The max number of hits per second.
259: * We increase the poll time to compensate and reduce the load. If this
260: * number is not at least half maxWaitingThreads then the USAGE_HIGH mode
261: * will not exist and the system will sublime from USAGE_LOW to USAGE_DIGG
262: */
263: protected int maxHitsPerSecond = 100;
264:
265: /**
266: * Static configuration data: What is the longest we wait for extra input
267: * after detecting output.
268: */
269: protected int maxConnectedTime = 60000;
270:
271: /**
272: * The system is under-utilized. Everyone does comet.
273: */
274: protected static final int USAGE_LOW = 0;
275:
276: /**
277: * This system can't cope with everyone on comet, we are in mixed mode.
278: */
279: protected static final int USAGE_HIGH = 1;
280:
281: /**
282: * The system is very heavily used, polling only.
283: */
284: protected static final int USAGE_DIGG = 2;
285:
286: /**
287: * Some Strings to help us give some debug output
288: */
289: protected static final String[] USAGE_NAMES = { "Low", "High",
290: "Digg" };
291:
292: /**
293: * What is the current usage mode.
294: */
295: protected int mode = USAGE_LOW;
296:
297: /**
298: * The time we are currently waiting before sending a browser away and
299: * asking it to reconnect.
300: */
301: protected int connectedTime = 60000;
302:
303: /**
304: * How long are we telling users to wait before they come back next
305: */
306: protected int disconnectedTime = 1000;
307:
308: /**
309: * We are recording the number of hits in the last 5 seconds.
310: * Maybe we should think about making this configurable.
311: */
312: protected static final int SECONDS_MONITORED = 10;
313:
314: /**
315: * Our record of the server loading
316: */
317: protected HitMonitor hitMonitor = new HitMonitor(SECONDS_MONITORED);
318:
319: /**
320: * How many sleepers are there?
321: */
322: protected int waitingThreads = 0;
323:
324: /**
325: * The log stream
326: */
327: private static final Log log = LogFactory
328: .getLog(DefaultServerLoadMonitor.class);
329: }
|