View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.concurrent.Executor;
25  import java.util.concurrent.TimeUnit;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import org.eclipse.jetty.io.EndPoint;
29  import org.eclipse.jetty.util.annotation.ManagedAttribute;
30  import org.eclipse.jetty.util.annotation.ManagedObject;
31  import org.eclipse.jetty.util.annotation.Name;
32  import org.eclipse.jetty.util.component.AbstractLifeCycle;
33  import org.eclipse.jetty.util.log.Log;
34  import org.eclipse.jetty.util.log.Logger;
35  import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
36  import org.eclipse.jetty.util.thread.Scheduler;
37  import org.eclipse.jetty.util.thread.ThreadPool;
38  
39  
40  /**
41   * A monitor for low resources
42   * <p>
43   * An instance of this class will monitor all the connectors of a server (or a set of connectors
44   * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
45   * <p>
46   * Low resources can be detected by:
47   * <ul>
48   * <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
49   * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
50   * <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
51   * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
52   * greater than {@link #getMaxMemory()}</li>
53   * <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
54   * of connections exceeds {@link #getMaxConnections()}</li>
55   * </ul>
56   * <p>
57   * Once low resources state is detected, the cause is logged and all existing connections returned
58   * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
59   * to {@link #getLowResourcesIdleTimeout()}.  New connections are not affected, however if the low
60   * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
61   * {@link #getLowResourcesIdleTimeout()} to all connections again.  Once the low resources state is
62   * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
63   */
64  @ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
65  public class LowResourceMonitor extends AbstractLifeCycle
66  {
67      private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
68      private final Server _server;
69      private Scheduler _scheduler;
70      private Connector[] _monitoredConnectors;
71      private int _period=1000;
72      private int _maxConnections;
73      private long _maxMemory;
74      private int _lowResourcesIdleTimeout=1000;
75      private int _maxLowResourcesTime=0;
76      private boolean _monitorThreads=true;
77      private final AtomicBoolean _low = new AtomicBoolean();
78      private String _cause;
79      private String _reasons;
80      private long _lowStarted;
81  
82      private final Runnable _monitor = new Runnable()
83      {
84          @Override
85          public void run()
86          {
87              if (isRunning())
88              {
89                  monitor();
90                  _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
91              }
92          }
93      };
94  
95      public LowResourceMonitor(@Name("server") Server server)
96      {
97          _server=server;
98      }
99  
100     @ManagedAttribute("Are the monitored connectors low on resources?")
101     public boolean isLowOnResources()
102     {
103         return _low.get();
104     }
105 
106     @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
107     public String getLowResourcesReasons()
108     {
109         return _reasons;
110     }
111 
112     @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
113     public long getLowResourcesStarted()
114     {
115         return _lowStarted;
116     }
117 
118     @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
119     public Collection<Connector> getMonitoredConnectors()
120     {
121         if (_monitoredConnectors==null)
122             return Collections.emptyList();
123         return Arrays.asList(_monitoredConnectors);
124     }
125 
126     /**
127      * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
128      */
129     public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
130     {
131         if (monitoredConnectors==null || monitoredConnectors.size()==0)
132             _monitoredConnectors=null;
133         else
134             _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
135     }
136 
137     @ManagedAttribute("The monitor period in ms")
138     public int getPeriod()
139     {
140         return _period;
141     }
142 
143     /**
144      * @param periodMS The period in ms to monitor for low resources
145      */
146     public void setPeriod(int periodMS)
147     {
148         _period = periodMS;
149     }
150 
151     @ManagedAttribute("True if low available threads status is monitored")
152     public boolean getMonitorThreads()
153     {
154         return _monitorThreads;
155     }
156 
157     /**
158      * @param monitorThreads If true, check connectors executors to see if they are
159      * {@link ThreadPool} instances that are low on threads.
160      */
161     public void setMonitorThreads(boolean monitorThreads)
162     {
163         _monitorThreads = monitorThreads;
164     }
165 
166     @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
167     public int getMaxConnections()
168     {
169         return _maxConnections;
170     }
171 
172     /**
173      * @param maxConnections The maximum connections before low resources state is triggered
174      */
175     public void setMaxConnections(int maxConnections)
176     {
177         _maxConnections = maxConnections;
178     }
179 
180     @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered.  Memory used is calculated as (totalMemory-freeMemory).")
181     public long getMaxMemory()
182     {
183         return _maxMemory;
184     }
185 
186     /**
187      * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
188      */
189     public void setMaxMemory(long maxMemoryBytes)
190     {
191         _maxMemory = maxMemoryBytes;
192     }
193 
194     @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
195     public int getLowResourcesIdleTimeout()
196     {
197         return _lowResourcesIdleTimeout;
198     }
199 
200     /**
201      * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
202      */
203     public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
204     {
205         _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
206     }
207 
208     @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
209     public int getMaxLowResourcesTime()
210     {
211         return _maxLowResourcesTime;
212     }
213 
214     /**
215      * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
216      */
217     public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
218     {
219         _maxLowResourcesTime = maxLowResourcesTimeMS;
220     }
221 
222     @Override
223     protected void doStart() throws Exception
224     {
225         _scheduler = _server.getBean(Scheduler.class);
226 
227         if (_scheduler==null)
228         {
229             _scheduler=new LRMScheduler();
230             _scheduler.start();
231         }
232         super.doStart();
233 
234         _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
235     }
236 
237     @Override
238     protected void doStop() throws Exception
239     {
240         if (_scheduler instanceof LRMScheduler)
241             _scheduler.stop();
242         super.doStop();
243     }
244 
245     protected Connector[] getMonitoredOrServerConnectors()
246     {
247         if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
248             return _monitoredConnectors;
249         return _server.getConnectors();
250     }
251 
252     protected void monitor()
253     {
254         String reasons=null;
255         String cause="";
256         int connections=0;
257 
258         ThreadPool serverThreads = _server.getThreadPool();
259         if (_monitorThreads && serverThreads.isLowOnThreads())
260         {
261             reasons=low(reasons,"Server low on threads: "+serverThreads);
262             cause+="S";
263         }
264 
265         for(Connector connector : getMonitoredOrServerConnectors())
266         {
267             connections+=connector.getConnectedEndPoints().size();
268 
269             Executor executor = connector.getExecutor();
270             if (executor instanceof ThreadPool && executor!=serverThreads)
271             {
272                 ThreadPool connectorThreads=(ThreadPool)executor;
273                 if (_monitorThreads && connectorThreads.isLowOnThreads())
274                 {
275                     reasons=low(reasons,"Connector low on threads: "+connectorThreads);
276                     cause+="T";
277                 }
278             }
279         }
280 
281         if (_maxConnections>0 && connections>_maxConnections)
282         {
283             reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
284             cause+="C";
285         }
286 
287         long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
288         if (_maxMemory>0 && memory>_maxMemory)
289         {
290             reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
291             cause+="M";
292         }
293 
294         if (reasons!=null)
295         {
296             // Log the reasons if there is any change in the cause
297             if (!cause.equals(_cause))
298             {
299                 LOG.warn("Low Resources: {}",reasons);
300                 _cause=cause;
301             }
302 
303             // Enter low resources state?
304             if (_low.compareAndSet(false,true))
305             {
306                 _reasons=reasons;
307                 _lowStarted=System.currentTimeMillis();
308                 setLowResources();
309             }
310 
311             // Too long in low resources state?
312             if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
313                 setLowResources();
314         }
315         else
316         {
317             if (_low.compareAndSet(true,false))
318             {
319                 LOG.info("Low Resources cleared");
320                 _reasons=null;
321                 _lowStarted=0;
322                 _cause=null;
323                 clearLowResources();
324             }
325         }
326     }
327 
328     protected void setLowResources()
329     {
330         for(Connector connector : getMonitoredOrServerConnectors())
331         {
332             for (EndPoint endPoint : connector.getConnectedEndPoints())
333                 endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
334         }
335     }
336 
337     protected void clearLowResources()
338     {
339         for(Connector connector : getMonitoredOrServerConnectors())
340         {
341             for (EndPoint endPoint : connector.getConnectedEndPoints())
342                 endPoint.setIdleTimeout(connector.getIdleTimeout());
343         }
344     }
345 
346     private String low(String reasons, String newReason)
347     {
348         if (reasons==null)
349             return newReason;
350         return reasons+", "+newReason;
351     }
352 
353 
354     private static class LRMScheduler extends ScheduledExecutorScheduler
355     {
356     }
357 }