KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > directwebremoting > impl > DefaultServerLoadMonitor


1 /*
2  * Copyright 2005 Joe Walker
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.directwebremoting.impl;
17
18 import org.directwebremoting.extend.ServerLoadMonitor;
19 import org.directwebremoting.extend.WaitController;
20 import org.directwebremoting.util.HitMonitor;
21 import org.directwebremoting.util.Logger;
22
23 /**
24  * A smart implementation of ServerLoadMonitor.
25  *
26  * <p>What a browser does:</p>
27  * <pre>
28  * connected disconnected connected ...
29  * ____________ ____________
30  * | | | |
31  * | | | |
32  * _____| |__________| |______
33  * [---cT---] [---dT---] [---cT---] ...
34  * </pre>
35  * <p>Where cT is the connectedTime and dT is the disconnectedTime.</p>
36  *
37  * <p>We impose some limits: a maximum number of symultaneously connected
38  * browsers <code>maxWaitingThreads</code>, and the maximum number of
39  * connections per second <code>maxHitsPerSecond</code>.</p>
40  *
41  * <p>We attempt to keep the actual waitingThreads and hitsPerSecond within
42  * bounds by vairying connectedTime and disconnectedTime.</p>
43  *
44  * <p>The system is in one of 3 modes: USAGE_LOW, USAGE_HIGH and USAGE_DIGG. The
45  * boundary between USAGE_LOW and USAGE_HIGH is called threadOut. The boundary
46  * between USAGE_HIGH and USAGE_DIGG is called hitOut.</p>
47  *
48  * <p>The system starts in USAGE_LOW mode. This mode uses constant values of
49  * connectedTime=60 secs and disconnectedTime=0 secs. We could use much bigger
50  * values for connectedTime (like infinite) however the servlet spec does not
51  * enable servlet engines to inform us if the browser goes away so we check by
52  * asking the browser to reconnect periodically.</p>
53  *
54  * <p>In USAGE_LOW mode we measure the number of clients using the number of
55  * concurrently connected browsers (waitingThreads), when this goes above
56  * maxWaitingThreads we move into USAGE_HIGH mode.</p>
57  *
58  * <p>On entering USAGE_HIGH mode, the settings (initially) change to
59  * connectedTime=49 secs and disconnectedTime=1 sec. As the load increases the
60  * connectedTime decreases linearly from 49 secs down to prevent the hits per
61  * second from going above maxHitsPerSecond. If the connectedTime goes below
62  * 1sec then the mode switches to USAGE_DIGG. If the connectedTime goes above
63  * 49 secs then mode switches to USAGE_LOW.</p>
64  *
65  * <p>Note: there is some danger of an overlap where the system toggles between
66  * USAGE_HIGH and USAGE_LOW. We need some way to prevent this from happening.
67  * </p>
68  *
69  * <p>On entering USAGE_DIGG mode, the connectedTime changes to 0 secs, and the
70  * disconnectedTime changes to 2 secs (to keep the round trip time at 2 secs).
71  * The disconnectedTime alters to prevent the hitsPerSecond from going above
72  * maxHitsPerSecond (In USAGE_HIGH mode the connectedTime was altered).
73  * When the disconnectedTime would go under 2 secs, we switch back to USAGE_HIGH
74  * mode.</p>
75  * @author Joe Walker [joe at getahead dot org]
76  */

77 public class DefaultServerLoadMonitor extends AbstractServerLoadMonitor implements ServerLoadMonitor
78 {
79     /* (non-Javadoc)
80      * @see org.directwebremoting.extend.ServerLoadMonitor#getConnectedTime()
81      */

82     public long getConnectedTime()
83     {
84         return connectedTime;
85     }
86
87     /* (non-Javadoc)
88      * @see org.directwebremoting.ServerLoadMonitor#timeToNextPoll()
89      */

90     public int getDisconnectedTime()
91     {
92         return disconnectedTime;
93     }
94
95     /* (non-Javadoc)
96      * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitStarting(org.directwebremoting.extend.WaitController)
97      */

98     public void threadWaitStarting(WaitController controller)
99     {
100         hitMonitor.recordHit();
101         waitingThreads++;
102         super.threadWaitStarting(controller);
103
104         checkLoading();
105     }
106
107     /* (non-Javadoc)
108      * @see org.directwebremoting.impl.AbstractServerLoadMonitor#threadWaitEnding(org.directwebremoting.extend.WaitController)
109      */

110     public void threadWaitEnding(WaitController controller)
111     {
112         waitingThreads--;
113         super.threadWaitEnding(controller);
114     }
115
116     /**
117      * Check that we are setting the time to next poll correctly.
118      */

119     private void checkLoading()
120     {
121         float hitsPerSecond = (float) hitMonitor.getHitsInLastPeriod() / SECONDS_MONITORED;
122
123         if (waitingThreads < maxWaitingThreads)
124         {
125             connectedTime = maxConnectedTime;
126             disconnectedTime = 0;
127
128             setMode(USAGE_LOW);
129             return;
130         }
131
132         int roundTripAtThreadOutSeconds = threadOutRoundTripTime / 1000;
133
134         int hitsPerSecondAtThreadOut = maxWaitingThreads / roundTripAtThreadOutSeconds;
135         int hitsPerSecondAtHitOut = maxHitsPerSecond;
136         
137         if (hitsPerSecond < hitsPerSecondAtThreadOut)
138         {
139             // We should probably be in USAGE_LOW mode, so we force the low
140
// end of the values in USAGE_HIGH mode
141
connectedTime = usageHighInitialConnectedTime;
142             disconnectedTime = usageHighDisconnectedTime;
143
144             setMode(USAGE_HIGH);
145             return;
146         }
147
148         if (mode == USAGE_DIGG)
149         {
150             // If we're getting close to the upper bound then slow down
151
float load = hitsPerSecond / maxHitsPerSecond;
152             connectedTime = usageDiggConnectedTime;
153             disconnectedTime = (int) (disconnectedTime * load);
154
155             // Check that USAGE_DIGG is the correct mode and we shouldn't change
156
if (disconnectedTime > usageDiggMinDisconnectedTime)
157             {
158                 setMode(USAGE_DIGG);
159                 return;
160             }
161
162             // So we were in USAGE_DIGG, but disconnectedTime was so low that we
163
// think USAGE_HIGH is a better mode to try
164
}
165
166         if (hitsPerSecond < hitsPerSecondAtHitOut)
167         {
168             // if hitsPerSecondAtThreadOut=0 and hitsPerSecondAtHitOut=1
169
// where would we score?
170
float factor = (float) waitingThreads / maxWaitingThreads;
171             connectedTime = (int) (connectedTime / factor);
172
173             if (connectedTime > usageHighInitialConnectedTime)
174             {
175                 connectedTime = usageHighInitialConnectedTime;
176             }
177
178             if (connectedTime < usageHighFinalConnectedTime)
179             {
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         {
195             disconnectedTime = usageDiggMinDisconnectedTime;
196         }
197
198         setMode(USAGE_DIGG);
199         return;
200     }
201
202     /**
203      * For debug purposes we keep a track of what mode we are in.
204      * @param mode The new usage mode
205      */

206     protected void setMode(int mode)
207     {
208         if (log.isDebugEnabled() && mode != this.mode)
209         {
210             log.debug("Changing modes, from " + USAGE_NAMES[this.mode] + " to " + USAGE_NAMES[mode]);
211         }
212
213         this.mode = mode;
214     }
215
216     /**
217      * @param maxWaitingThreads the maxWaitingThreads to set
218      */

219     public void setMaxWaitingThreads(int maxWaitingThreads)
220     {
221         this.maxWaitingThreads = maxWaitingThreads;
222     }
223
224     /**
225      * @param maxHitsPerSecond the maxHitsPerSecond to set
226      */

227     public void setMaxHitsPerSecond(int maxHitsPerSecond)
228     {
229         this.maxHitsPerSecond = maxHitsPerSecond;
230     }
231
232     /**
233      * It might be good top expose this, however there are currently assumptions
234      * in the code that the value is set to 60000.
235      * See {@link #usageHighInitialConnectedTime}.
236      * @param maxConnectedTime the maxConnectedTime to set
237      */

238     void setMaxConnectedTime(int maxConnectedTime)
239     {
240         this.maxConnectedTime = maxConnectedTime;
241     }
242
243     /**
244      *
245      */

246     protected static final int usageHighDisconnectedTime = 1000;
247     protected static final int usageHighInitialConnectedTime = 49000;
248     protected static final int usageHighFinalConnectedTime = 1000;
249     protected static final int usageDiggConnectedTime = 0;
250     protected static final int usageDiggMinDisconnectedTime = usageHighDisconnectedTime + usageHighFinalConnectedTime;
251     protected static final int hitOutRoundTripTime = usageHighDisconnectedTime + usageHighFinalConnectedTime;
252     protected static final int threadOutRoundTripTime = usageHighInitialConnectedTime + usageHighDisconnectedTime;
253
254     /**
255      * Static configuration data: The max number of threads we keep waiting.
256      * We reduce the timeWithinPoll*Stream variables to reduce the load
257      */

258     protected int maxWaitingThreads = 100;
259
260     /**
261      * Static configuration data: The max number of hits per second.
262      * We increase the poll time to compensate and reduce the load. If this
263      * number is not at least half maxWaitingThreads then the USAGE_HIGH mode
264      * will not exist and the system will sublime from USAGE_LOW to USAGE_DIGG
265      */

266     protected int maxHitsPerSecond = 100;
267
268     /**
269      * Static configuration data: What is the longest we wait for extra input
270      * after detecting output.
271      */

272     protected int maxConnectedTime = 60000;
273
274     /**
275      * The system is under-utilized. Everyone does comet.
276      */

277     protected static final int USAGE_LOW = 0;
278
279     /**
280      * This system can't cope with everyone on comet, we are in mixed mode.
281      */

282     protected static final int USAGE_HIGH = 1;
283
284     /**
285      * The system is very heavily used, polling only.
286      */

287     protected static final int USAGE_DIGG = 2;
288
289     /**
290      * Some Strings to help us give some debug output
291      */

292     protected static final String JavaDoc[] USAGE_NAMES = { "Low", "High", "Digg" };
293
294     /**
295      * What is the current usage mode.
296      */

297     protected int mode = USAGE_LOW;
298
299     /**
300      * The time we are currently waiting before sending a browser away and
301      * asking it to reconnect.
302      */

303     protected int connectedTime = 60000;
304
305     /**
306      * How long are we telling users to wait before they come back next
307      */

308     protected int disconnectedTime = 1000;
309
310     /**
311      * We are recording the number of hits in the last 5 seconds.
312      * Maybe we should think about making this configurable.
313      */

314     protected static final int SECONDS_MONITORED = 10;
315
316     /**
317      * Our record of the server loading
318      */

319     protected HitMonitor hitMonitor = new HitMonitor(SECONDS_MONITORED);
320
321     /**
322      * How many sleepers are there?
323      */

324     protected int waitingThreads = 0;
325
326     /**
327      * The log stream
328      */

329     private static final Logger log = Logger.getLogger(DefaultServerLoadMonitor.class);
330 }
331
Popular Tags