View Javadoc

1   /**
2      This file is part of GoldenGate Project (named also GoldenGate or GG).
3   
4      Copyright 2009, Frederic Bregier, and individual contributors by the @author
5      tags. See the COPYRIGHT.txt in the distribution for a full listing of
6      individual contributors.
7   
8      All GoldenGate Project is free software: you can redistribute it and/or 
9      modify it under the terms of the GNU General Public License as published 
10     by the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12  
13     GoldenGate is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17  
18     You should have received a copy of the GNU General Public License
19     along with GoldenGate .  If not, see <http://www.gnu.org/licenses/>.
20   */
21  package goldengate.common.cpu;
22  
23  import goldengate.common.database.DbAdmin;
24  import goldengate.common.logging.GgInternalLogger;
25  import goldengate.common.logging.GgInternalLoggerFactory;
26  
27  import java.util.LinkedList;
28  import java.util.Random;
29  import java.util.concurrent.ScheduledThreadPoolExecutor;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.jboss.netty.handler.traffic.GlobalTrafficShapingHandler;
33  
34  /**
35   * Abstract class for Constraint Limit Handler for GoldenGate project
36   * 
37   * @author Frederic Bregier
38   *
39   */
40  public abstract class GgConstraintLimitHandler implements Runnable {
41      /**
42       * Internal Logger
43       */
44      private static final GgInternalLogger logger = GgInternalLoggerFactory
45              .getLogger(GgConstraintLimitHandler.class);
46      
47      private static final String NOALERT = "noAlert";
48      public String lastAlert = NOALERT;
49      private boolean constraintInactive = true;
50      private boolean useCpuLimits = false;
51      
52      private final Random random = new Random();
53      private CpuManagementInterface cpuManagement;
54      private double cpuLimit = 0.8;
55      private int channelLimit = 1000;
56      private boolean isServer = false;
57      private double lastLA = 0.0;
58      private long lastTime;
59      
60      // Dynamic throttling
61      private long WAITFORNETOP = 1000;
62      private long TIMEOUTCON = 10000;
63      private double highCpuLimit = 0.8;
64      private double lowCpuLimit = 0.5;
65      private double percentageDecreaseRatio = 0.25;
66      private long delay = 1000;
67      private long limitLowBandwidth = 10000;
68      private GlobalTrafficShapingHandler handler;
69      private ScheduledThreadPoolExecutor executor = null;
70      private static class CurLimits {
71          long read;
72          long write;
73          private CurLimits(long read, long write) {
74              this.read = read;
75              this.write = write;
76          }
77      }
78      private final LinkedList<CurLimits> curLimits = new LinkedList<GgConstraintLimitHandler.CurLimits>();
79      private int nbSinceLastDecrease = 0;
80      private static final int payload = 5; // 5 seconds of payload when new high cpu
81      /**
82       * Empty constructor
83       */
84      public GgConstraintLimitHandler() {
85          // Do nothing except setup standard value for inactivity
86          if (cpuManagement == null)
87              cpuManagement = new CpuManagementNoInfo();
88      }
89      /**
90       * This constructor enables only throttling bandwidth with cpu usage
91       * 
92       * 
93       * @param WAITFORNETOP2 1000 ms as wait for a network operation
94       * @param TIMEOUTCON2 10000 ms as timeout limit
95       * @param useJdkCpuLimit True to use JDK Cpu native or False for JavaSysMon
96       * @param lowcpuLimit for proactive cpu limitation (throttling bandwidth) (0<= x < 1 & highcpulimit) 
97       * @param highcpuLimit for proactive cpu limitation (throttling bandwidth) (0<= x <= 1) 0 meaning no throttle activated
98       * @param percentageDecrease for proactive cpu limitation, throttling bandwidth reduction (0 < x < 1) as 0.25 for 25% of reduction
99       * @param handler the GlobalTrafficShapingHandler associated (null to have no proactive cpu limitation)
100      * @param delay the delay between 2 tests for proactive cpu limitation
101      * @param limitLowBandwidth the minimal bandwidth (read or write) to apply when decreasing bandwidth (low limit = 4096)
102      */
103     public GgConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, boolean useJdkCpuLimit,
104             double lowcpuLimit, double highcpuLimit, double percentageDecrease, 
105             GlobalTrafficShapingHandler handler, long delay, long limitLowBandwidth) {
106         this(WAITFORNETOP2, TIMEOUTCON2, 
107                 true, useJdkCpuLimit, 0, 0,
108                 lowcpuLimit, highcpuLimit, percentageDecrease, 
109                 handler, delay, limitLowBandwidth);
110     }
111     /**
112      * This constructor enables only Connection check ability
113      * 
114      * @param useCpuLimit True to enable cpuLimit on connection check
115      * @param useJdKCpuLimit True to use JDK Cpu native or False for JavaSysMon
116      * @param cpulimit high cpu limit (0<= x < 1) to refuse new connections
117      * @param channellimit number of connection limit (0<= x)
118      */
119     public GgConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, boolean useCpuLimit, 
120             boolean useJdKCpuLimit, double cpulimit, int channellimit) {
121         this(WAITFORNETOP2, TIMEOUTCON2, useCpuLimit, useJdKCpuLimit, cpulimit, channellimit,
122                 0,0,0.01,null,1000000,4096);
123     }
124     /**
125      * This constructor enables both Connection check ability and throttling bandwidth with cpu usage
126      * 
127      * @param WAITFORNETOP2 1000 ms as wait for a network operation
128      * @param TIMEOUTCON2 10000 ms as timeout limit
129      * @param useCpuLimit True to enable cpuLimit on connection check
130      * @param useJdKCpuLimit True to use JDK Cpu native or False for JavaSysMon
131      * @param cpulimit high cpu limit (0<= x < 1) to refuse new connections
132      * @param channellimit number of connection limit (0<= x)
133      * @param lowcpuLimit for proactive cpu limitation (throttling bandwidth) (0<= x < 1 & highcpulimit) 
134      * @param highcpuLimit for proactive cpu limitation (throttling bandwidth) (0<= x <= 1) 0 meaning no throttle activated
135      * @param percentageDecrease for proactive cpu limitation, throttling bandwidth reduction (0 < x < 1) as 0.25 for 25% of reduction
136      * @param handler the GlobalTrafficShapingHandler associated (null to have no proactive cpu limitation)
137      * @param delay the delay between 2 tests for proactive cpu limitation
138      * @param limitLowBandwidth the minimal bandwidth (read or write) to apply when decreasing bandwidth (low limit = 4096)
139      */
140     public GgConstraintLimitHandler(long WAITFORNETOP2, long TIMEOUTCON2, 
141             boolean useCpuLimit, 
142             boolean useJdKCpuLimit, double cpulimit, int channellimit,
143             double lowcpuLimit, double highcpuLimit, double percentageDecrease, 
144             GlobalTrafficShapingHandler handler, long delay, long limitLowBandwidth) {
145         useCpuLimits = useCpuLimit;
146         WAITFORNETOP = WAITFORNETOP2;
147         TIMEOUTCON = TIMEOUTCON2;
148         lowCpuLimit = lowcpuLimit;
149         highCpuLimit = highcpuLimit;
150         this.limitLowBandwidth = limitLowBandwidth;
151         if (this.limitLowBandwidth < 4096) {
152             this.limitLowBandwidth = 4096;
153         }
154         this.delay = delay;
155         if (lowCpuLimit <= 0) {
156             lowCpuLimit = highCpuLimit / 2;
157         }
158         percentageDecreaseRatio = percentageDecrease;
159         if (percentageDecreaseRatio <=0) {
160             percentageDecreaseRatio = 0.25;
161         } else if (percentageDecreaseRatio >= 1) {
162             percentageDecreaseRatio /= 100;
163         }
164         if (delay < WAITFORNETOP >> 1) {
165             this.delay = WAITFORNETOP;
166         }
167         this.handler = handler;
168         if (useCpuLimits || highCpuLimit > 0) {
169             constraintInactive = false;
170             if (useJdKCpuLimit) {
171                 try {
172                     cpuManagement = new CpuManagement();
173                 } catch (IllegalArgumentException e) {
174                     cpuManagement = new CpuManagementNoInfo();
175                 }
176             } else {
177                 cpuManagement = new CpuManagementSysmon();
178             }
179         } else {
180             // no test at all
181             constraintInactive = true;
182             cpuManagement = new CpuManagementNoInfo();
183         }
184         cpuLimit = cpulimit;
185         channelLimit = channellimit;
186         lastTime = System.currentTimeMillis();
187         if (this.handler != null && (!constraintInactive)) {
188             executor = new ScheduledThreadPoolExecutor(1);
189             executor.scheduleWithFixedDelay(this, this.delay, this.delay, TimeUnit.MILLISECONDS);
190         }
191     }
192     /**
193      * Release the resources
194      */
195     public void release() {
196         if (this.executor != null) {
197             this.executor.shutdownNow();
198         }
199     }
200     /**
201      * To explicitly set this handler as server mode
202      * @param isServer
203      */
204     public void setServer(boolean isServer) {
205         this.isServer = isServer;
206     }
207     private double getLastLA() {
208         long newTime = System.currentTimeMillis();
209         // first check if last test was done too shortly
210         if ((newTime - lastTime) < (WAITFORNETOP >> 1)) {
211             // If last test was wrong, then redo the test
212             if (lastLA <= cpuLimit) {
213                 // last test was OK, so Continue
214                 return lastLA;
215             }
216         }
217         lastTime = newTime;
218         lastLA = cpuManagement.getLoadAverage();
219         return lastLA;
220     }
221     /**
222      * 
223      * @return True if one of the limit is exceeded. Always False if not a server mode
224      */
225     public boolean checkConstraints() {
226         if (! isServer)
227             return false;
228         if ((useCpuLimits) && cpuLimit < 1 && cpuLimit > 0) {
229             getLastLA();
230             if (lastLA <= cpuLimit) {
231                 lastAlert = NOALERT;
232                 return false;
233             }
234             if (lastLA > cpuLimit) {
235                 lastAlert = "CPU Constraint: "+lastLA+" > "+cpuLimit;
236                 logger.debug(lastAlert);
237                 return true;
238             }
239         }
240         if (channelLimit > 0) {
241             int nb = DbAdmin.getNbConnection()-DbAdmin.nbHttpSession;
242             if (channelLimit < nb) {
243                 lastAlert = "Network Constraint: "+nb+" > "+channelLimit;
244                 logger.debug(lastAlert);
245                 return true;
246             }
247             nb = getNumberLocalChannel();
248             if (channelLimit < nb) {
249                 lastAlert = "LocalNetwork Constraint: "+nb+" > "+channelLimit;
250                 logger.debug(lastAlert);
251                 return true;
252             }
253         }
254         lastAlert = NOALERT;
255         return false;
256     }
257     /**
258      * 
259      * @return the current number of active Local Channel 
260      */
261     protected abstract int getNumberLocalChannel();
262     
263     /**
264      * Same as checkConstraints except that the thread will sleep some time proportionally to
265      * the current Load (if CPU related)
266      * @param step the current step in retry
267      * @return True if one of the limit is exceeded. Always False if not a server mode
268      */
269     public boolean checkConstraintsSleep(int step) {
270         if (! isServer)
271             return false;
272         long delay = WAITFORNETOP >> 1;
273         if ((useCpuLimits) && cpuLimit < 1 && cpuLimit > 0) {
274             long newTime = System.currentTimeMillis();
275             // first check if last test was done too shortly
276             if ((newTime - lastTime) < delay) {
277                 // If last test was wrong, then wait a bit then redo the test
278                 if (lastLA > cpuLimit) {
279                     double sleep = lastLA * delay * (step+1) * random.nextFloat();
280                     long shorttime = (((long) sleep)/10)*10;
281                     try {
282                         Thread.sleep(shorttime);
283                     } catch (InterruptedException e) {
284                     }
285                 } else {
286                     // last test was OK, so Continue
287                     lastAlert = NOALERT;
288                     return false;
289                 }
290             }
291         }
292         if (checkConstraints()) {
293             delay = getSleepTime()*(step+1);
294             try {
295                 Thread.sleep(delay);
296             } catch (InterruptedException e) {
297             }
298             return true;
299         } else {
300             lastAlert = NOALERT;
301             return false;
302         }
303     }
304 
305     /**
306      * 
307      * @return a time below TIMEOUTCON with a random
308      */
309     public long getSleepTime() {
310         return (((long) (TIMEOUTCON*random.nextFloat())+5000)/10)*10;
311     }
312     /**
313      * @return the cpuLimit
314      */
315     public double getCpuLimit() {
316         return cpuLimit;
317     }
318 
319     /**
320      * @param cpuLimit the cpuLimit to set
321      */
322     public void setCpuLimit(double cpuLimit) {
323         this.cpuLimit = cpuLimit;
324     }
325 
326     /**
327      * @return the channelLimit
328      */
329     public int getChannelLimit() {
330         return channelLimit;
331     }
332 
333     /**
334      * @param channelLimit the channelLimit to set
335      */
336     public void setChannelLimit(int channelLimit) {
337         this.channelLimit = channelLimit;
338     }
339     /**
340      * Get the current setting on Read Limit (supposed to be not the value in the handler but in the configuration)
341      * @return the current setting on Read Limit
342      */
343     protected abstract long getReadLimit();
344     /**
345      * Get the current setting on Write Limit (supposed to be not the value in the handler but in the configuration)
346      * @return the current setting on Write Limit
347      */
348     protected abstract long getWriteLimit();
349     /**
350      * Set the handler
351      * @param handler
352      */
353     public void setHandler(GlobalTrafficShapingHandler handler) {
354         this.handler = handler;
355         if ((!constraintInactive) && this.handler != null && highCpuLimit > 0) {
356             if (executor != null) {
357                 executor.shutdownNow();
358             }
359             logger.debug("Activate Throttle bandwidth according to CPU usage");
360             executor = new ScheduledThreadPoolExecutor(1);
361             executor.scheduleWithFixedDelay(this, this.delay, this.delay, TimeUnit.MILLISECONDS);
362         } else {
363             if (executor != null) {
364                 executor.shutdownNow();
365                 executor = null;
366             }
367         }
368     }
369     /**
370      * Check every delay if the current cpu usage needs to relax or to constraint the bandwidth
371      */
372     public void run() {
373         if (constraintInactive)
374             return;
375         double curLA = getLastLA();
376         if (curLA > highCpuLimit) {
377             CurLimits curlimit = null;
378             if (curLimits.isEmpty()) {
379                 // get current limit setting
380                 curlimit = new CurLimits(getReadLimit(), getWriteLimit());
381                 if (curlimit.read == 0) {
382                     // take the current bandwidth
383                     curlimit.read = handler.getTrafficCounter().getLastReadThroughput();
384                     if (curlimit.read < limitLowBandwidth){
385                         curlimit.read = 0;
386                     }
387                 }
388                 if (curlimit.write == 0) {
389                     // take the current bandwidth
390                     curlimit.write = handler.getTrafficCounter().getLastWriteThroughput();
391                     if (curlimit.write < limitLowBandwidth){
392                         curlimit.write = 0;
393                     }
394                 }
395             } else {
396                 curlimit = curLimits.getLast();
397             }
398             long newread = (long) (curlimit.read*(1-percentageDecreaseRatio));
399             if (newread < limitLowBandwidth) {
400                 newread = limitLowBandwidth;
401             }
402             long newwrite = (long)(curlimit.write*(1-percentageDecreaseRatio));
403             if (newwrite < limitLowBandwidth) {
404                 newwrite = limitLowBandwidth;
405             }
406             CurLimits newlimit = new CurLimits(newread, newwrite);
407             if (curLimits.isEmpty() || curlimit.read != newread || curlimit.write != newwrite) {
408                 // Not same limit so add this limit
409                 curLimits.add(newlimit);
410                 logger.debug("Set new low limit since CPU = "+curLA+" "+newwrite+":"+newread);
411                 handler.configure(newlimit.write, newlimit.read);
412                 nbSinceLastDecrease += payload;
413             }
414         } else if (curLA < lowCpuLimit) {
415             if (curLimits.isEmpty()) {
416                 // nothing to do
417                 return;
418             }
419             if (nbSinceLastDecrease > 0) {
420                 nbSinceLastDecrease--;
421                 // wait a bit more in case
422                 return;
423             }
424             nbSinceLastDecrease = 0;
425             curLimits.pollLast();
426             CurLimits newlimit = null;
427             if (curLimits.isEmpty()) {
428                 // reset to default limits
429                 long newread = getReadLimit();
430                 long newwrite = getWriteLimit();
431                 logger.debug("restore limit since CPU = "+curLA+" "+newwrite+":"+newread);
432                 handler.configure(newwrite, newread);
433             } else {
434                 // set next upper values
435                 newlimit = curLimits.getLast();
436                 long newread = newlimit.read;
437                 long newwrite = newlimit.write;
438                 logger.debug("Set new upper limit since CPU = "+curLA+" "+newwrite+":"+newread);
439                 handler.configure(newwrite, newread);
440                 // give extra payload to prevent a brutal return to normal 
441                 nbSinceLastDecrease = payload;
442             }
443         }
444     }
445 }