View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.handler;
20  
21  import java.io.IOException;
22  import java.util.concurrent.Future;
23  import java.util.concurrent.TimeoutException;
24  import java.util.concurrent.atomic.AtomicInteger;
25  import java.util.concurrent.atomic.AtomicLong;
26  import java.util.concurrent.atomic.AtomicReference;
27  
28  import javax.servlet.AsyncEvent;
29  import javax.servlet.AsyncListener;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.server.AsyncContextEvent;
35  import org.eclipse.jetty.server.HttpChannelState;
36  import org.eclipse.jetty.server.Request;
37  import org.eclipse.jetty.server.Response;
38  import org.eclipse.jetty.util.FutureCallback;
39  import org.eclipse.jetty.util.annotation.ManagedAttribute;
40  import org.eclipse.jetty.util.annotation.ManagedObject;
41  import org.eclipse.jetty.util.annotation.ManagedOperation;
42  import org.eclipse.jetty.util.component.Graceful;
43  import org.eclipse.jetty.util.statistic.CounterStatistic;
44  import org.eclipse.jetty.util.statistic.SampleStatistic;
45  
46  @ManagedObject("Request Statistics Gathering")
47  public class StatisticsHandler extends HandlerWrapper implements Graceful
48  {
49      private final AtomicLong _statsStartedAt = new AtomicLong();
50  
51      private final CounterStatistic _requestStats = new CounterStatistic();
52      private final SampleStatistic _requestTimeStats = new SampleStatistic();
53      private final CounterStatistic _dispatchedStats = new CounterStatistic();
54      private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
55      private final CounterStatistic _asyncWaitStats = new CounterStatistic();
56  
57      private final AtomicInteger _asyncDispatches = new AtomicInteger();
58      private final AtomicInteger _expires = new AtomicInteger();
59  
60      private final AtomicInteger _responses1xx = new AtomicInteger();
61      private final AtomicInteger _responses2xx = new AtomicInteger();
62      private final AtomicInteger _responses3xx = new AtomicInteger();
63      private final AtomicInteger _responses4xx = new AtomicInteger();
64      private final AtomicInteger _responses5xx = new AtomicInteger();
65      private final AtomicLong _responsesTotalBytes = new AtomicLong();
66  
67      private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
68      
69      private final AsyncListener _onCompletion = new AsyncListener()
70      {
71          @Override
72          public void onTimeout(AsyncEvent event) throws IOException
73          {
74              _expires.incrementAndGet();
75          }
76          
77          @Override
78          public void onStartAsync(AsyncEvent event) throws IOException
79          {
80              event.getAsyncContext().addListener(this);
81          }
82          
83          @Override
84          public void onError(AsyncEvent event) throws IOException
85          {
86          }
87  
88          @Override
89          public void onComplete(AsyncEvent event) throws IOException
90          {
91              HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
92  
93              Request request = state.getBaseRequest();
94              final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
95  
96              long d=_requestStats.decrement();
97              _requestTimeStats.set(elapsed);
98  
99              updateResponse(request);
100 
101             if (!state.isDispatched())
102                 _asyncWaitStats.decrement();
103             
104             // If we have no more dispatches, should we signal shutdown?
105             if (d==0)
106             {
107                 FutureCallback shutdown = _shutdown.get();
108                 if (shutdown!=null)
109                     shutdown.succeeded();
110             }   
111         }
112     };
113 
114     /**
115      * Resets the current request statistics.
116      */
117     @ManagedOperation(value="resets statistics", impact="ACTION")
118     public void statsReset()
119     {
120         _statsStartedAt.set(System.currentTimeMillis());
121 
122         _requestStats.reset();
123         _requestTimeStats.reset();
124         _dispatchedStats.reset();
125         _dispatchedTimeStats.reset();
126         _asyncWaitStats.reset();
127 
128         _asyncDispatches.set(0);
129         _expires.set(0);
130         _responses1xx.set(0);
131         _responses2xx.set(0);
132         _responses3xx.set(0);
133         _responses4xx.set(0);
134         _responses5xx.set(0);
135         _responsesTotalBytes.set(0L);
136     }
137 
138     @Override
139     public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
140     {
141         _dispatchedStats.increment();
142 
143         final long start;
144         HttpChannelState state = request.getHttpChannelState();
145         if (state.isInitial())
146         {
147             // new request
148             _requestStats.increment();
149             start = request.getTimeStamp();
150         }
151         else
152         {
153             // resumed request
154             start = System.currentTimeMillis();
155             _asyncWaitStats.decrement();
156             if (state.isDispatched())
157                 _asyncDispatches.incrementAndGet();
158         }
159 
160         try
161         {
162             super.handle(path, request, httpRequest, httpResponse);
163         }
164         finally
165         {
166             final long now = System.currentTimeMillis();
167             final long dispatched=now-start;
168 
169             _dispatchedStats.decrement();
170             _dispatchedTimeStats.set(dispatched);
171 
172             if (state.isSuspended())
173             {
174                 if (state.isInitial())
175                     state.addListener(_onCompletion);
176                 _asyncWaitStats.increment();
177             }
178             else if (state.isInitial())
179             {
180                 long d=_requestStats.decrement();
181                 _requestTimeStats.set(dispatched);
182                 updateResponse(request);
183                 
184                 // If we have no more dispatches, should we signal shutdown?
185                 FutureCallback shutdown = _shutdown.get();
186                 if (shutdown!=null)
187                 {
188                     httpResponse.flushBuffer();
189                     if (d==0)
190                         shutdown.succeeded();
191                 }   
192             }
193             // else onCompletion will handle it.
194         }
195     }
196 
197     private void updateResponse(Request request)
198     {
199         Response response = request.getResponse();
200         switch (response.getStatus() / 100)
201         {
202             case 0:
203                 if (request.isHandled())
204                     _responses2xx.incrementAndGet();
205                 else
206                     _responses4xx.incrementAndGet();
207                 break;
208             case 1:
209                 _responses1xx.incrementAndGet();
210                 break;
211             case 2:
212                 _responses2xx.incrementAndGet();
213                 break;
214             case 3:
215                 _responses3xx.incrementAndGet();
216                 break;
217             case 4:
218                 _responses4xx.incrementAndGet();
219                 break;
220             case 5:
221                 _responses5xx.incrementAndGet();
222                 break;
223             default:
224                 break;
225         }
226         _responsesTotalBytes.addAndGet(response.getContentCount());
227     }
228 
229     @Override
230     protected void doStart() throws Exception
231     {
232         _shutdown.set(null);
233         super.doStart();
234         statsReset();
235     }
236     
237 
238     @Override
239     protected void doStop() throws Exception
240     {
241         super.doStop();
242         FutureCallback shutdown = _shutdown.get();
243         if (shutdown!=null && !shutdown.isDone())
244             shutdown.failed(new TimeoutException());
245     }
246 
247     /**
248      * @return the number of requests handled by this handler
249      * since {@link #statsReset()} was last called, excluding
250      * active requests
251      * @see #getAsyncDispatches()
252      */
253     @ManagedAttribute("number of requests")
254     public int getRequests()
255     {
256         return (int)_requestStats.getTotal();
257     }
258 
259     /**
260      * @return the number of requests currently active.
261      * since {@link #statsReset()} was last called.
262      */
263     @ManagedAttribute("number of requests currently active")
264     public int getRequestsActive()
265     {
266         return (int)_requestStats.getCurrent();
267     }
268 
269     /**
270      * @return the maximum number of active requests
271      * since {@link #statsReset()} was last called.
272      */
273     @ManagedAttribute("maximum number of active requests")
274     public int getRequestsActiveMax()
275     {
276         return (int)_requestStats.getMax();
277     }
278 
279     /**
280      * @return the maximum time (in milliseconds) of request handling
281      * since {@link #statsReset()} was last called.
282      */
283     @ManagedAttribute("maximum time spend handling requests (in ms)")
284     public long getRequestTimeMax()
285     {
286         return _requestTimeStats.getMax();
287     }
288 
289     /**
290      * @return the total time (in milliseconds) of requests handling
291      * since {@link #statsReset()} was last called.
292      */
293     @ManagedAttribute("total time spend in all request handling (in ms)")
294     public long getRequestTimeTotal()
295     {
296         return _requestTimeStats.getTotal();
297     }
298 
299     /**
300      * @return the mean time (in milliseconds) of request handling
301      * since {@link #statsReset()} was last called.
302      * @see #getRequestTimeTotal()
303      * @see #getRequests()
304      */
305     @ManagedAttribute("mean time spent handling requests (in ms)")
306     public double getRequestTimeMean()
307     {
308         return _requestTimeStats.getMean();
309     }
310 
311     /**
312      * @return the standard deviation of time (in milliseconds) of request handling
313      * since {@link #statsReset()} was last called.
314      * @see #getRequestTimeTotal()
315      * @see #getRequests()
316      */
317     @ManagedAttribute("standard deviation for request handling (in ms)")
318     public double getRequestTimeStdDev()
319     {
320         return _requestTimeStats.getStdDev();
321     }
322 
323     /**
324      * @return the number of dispatches seen by this handler
325      * since {@link #statsReset()} was last called, excluding
326      * active dispatches
327      */
328     @ManagedAttribute("number of dispatches")
329     public int getDispatched()
330     {
331         return (int)_dispatchedStats.getTotal();
332     }
333 
334     /**
335      * @return the number of dispatches currently in this handler
336      * since {@link #statsReset()} was last called, including
337      * resumed requests
338      */
339     @ManagedAttribute("number of dispatches currently active")
340     public int getDispatchedActive()
341     {
342         return (int)_dispatchedStats.getCurrent();
343     }
344 
345     /**
346      * @return the max number of dispatches currently in this handler
347      * since {@link #statsReset()} was last called, including
348      * resumed requests
349      */
350     @ManagedAttribute("maximum number of active dispatches being handled")
351     public int getDispatchedActiveMax()
352     {
353         return (int)_dispatchedStats.getMax();
354     }
355 
356     /**
357      * @return the maximum time (in milliseconds) of request dispatch
358      * since {@link #statsReset()} was last called.
359      */
360     @ManagedAttribute("maximum time spend in dispatch handling")
361     public long getDispatchedTimeMax()
362     {
363         return _dispatchedTimeStats.getMax();
364     }
365 
366     /**
367      * @return the total time (in milliseconds) of requests handling
368      * since {@link #statsReset()} was last called.
369      */
370     @ManagedAttribute("total time spent in dispatch handling (in ms)")
371     public long getDispatchedTimeTotal()
372     {
373         return _dispatchedTimeStats.getTotal();
374     }
375 
376     /**
377      * @return the mean time (in milliseconds) of request handling
378      * since {@link #statsReset()} was last called.
379      * @see #getRequestTimeTotal()
380      * @see #getRequests()
381      */
382     @ManagedAttribute("mean time spent in dispatch handling (in ms)")
383     public double getDispatchedTimeMean()
384     {
385         return _dispatchedTimeStats.getMean();
386     }
387 
388     /**
389      * @return the standard deviation of time (in milliseconds) of request handling
390      * since {@link #statsReset()} was last called.
391      * @see #getRequestTimeTotal()
392      * @see #getRequests()
393      */
394     @ManagedAttribute("standard deviation for dispatch handling (in ms)")
395     public double getDispatchedTimeStdDev()
396     {
397         return _dispatchedTimeStats.getStdDev();
398     }
399 
400     /**
401      * @return the number of requests handled by this handler
402      * since {@link #statsReset()} was last called, including
403      * resumed requests
404      * @see #getAsyncDispatches()
405      */
406     @ManagedAttribute("total number of async requests")
407     public int getAsyncRequests()
408     {
409         return (int)_asyncWaitStats.getTotal();
410     }
411 
412     /**
413      * @return the number of requests currently suspended.
414      * since {@link #statsReset()} was last called.
415      */
416     @ManagedAttribute("currently waiting async requests")
417     public int getAsyncRequestsWaiting()
418     {
419         return (int)_asyncWaitStats.getCurrent();
420     }
421 
422     /**
423      * @return the maximum number of current suspended requests
424      * since {@link #statsReset()} was last called.
425      */
426     @ManagedAttribute("maximum number of waiting async requests")
427     public int getAsyncRequestsWaitingMax()
428     {
429         return (int)_asyncWaitStats.getMax();
430     }
431 
432     /**
433      * @return the number of requests that have been asynchronously dispatched
434      */
435     @ManagedAttribute("number of requested that have been asynchronously dispatched")
436     public int getAsyncDispatches()
437     {
438         return _asyncDispatches.get();
439     }
440 
441     /**
442      * @return the number of requests that expired while suspended.
443      * @see #getAsyncDispatches()
444      */
445     @ManagedAttribute("number of async requests requests that have expired")
446     public int getExpires()
447     {
448         return _expires.get();
449     }
450 
451     /**
452      * @return the number of responses with a 1xx status returned by this context
453      * since {@link #statsReset()} was last called.
454      */
455     @ManagedAttribute("number of requests with 1xx response status")
456     public int getResponses1xx()
457     {
458         return _responses1xx.get();
459     }
460 
461     /**
462      * @return the number of responses with a 2xx status returned by this context
463      * since {@link #statsReset()} was last called.
464      */
465     @ManagedAttribute("number of requests with 2xx response status")
466     public int getResponses2xx()
467     {
468         return _responses2xx.get();
469     }
470 
471     /**
472      * @return the number of responses with a 3xx status returned by this context
473      * since {@link #statsReset()} was last called.
474      */
475     @ManagedAttribute("number of requests with 3xx response status")
476     public int getResponses3xx()
477     {
478         return _responses3xx.get();
479     }
480 
481     /**
482      * @return the number of responses with a 4xx status returned by this context
483      * since {@link #statsReset()} was last called.
484      */
485     @ManagedAttribute("number of requests with 4xx response status")
486     public int getResponses4xx()
487     {
488         return _responses4xx.get();
489     }
490 
491     /**
492      * @return the number of responses with a 5xx status returned by this context
493      * since {@link #statsReset()} was last called.
494      */
495     @ManagedAttribute("number of requests with 5xx response status")
496     public int getResponses5xx()
497     {
498         return _responses5xx.get();
499     }
500 
501     /**
502      * @return the milliseconds since the statistics were started with {@link #statsReset()}.
503      */
504     @ManagedAttribute("time in milliseconds stats have been collected for")
505     public long getStatsOnMs()
506     {
507         return System.currentTimeMillis() - _statsStartedAt.get();
508     }
509 
510     /**
511      * @return the total bytes of content sent in responses
512      */
513     @ManagedAttribute("total number of bytes across all responses")
514     public long getResponsesBytesTotal()
515     {
516         return _responsesTotalBytes.get();
517     }
518 
519     public String toStatsHTML()
520     {
521         StringBuilder sb = new StringBuilder();
522 
523         sb.append("<h1>Statistics:</h1>\n");
524         sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n");
525 
526         sb.append("<h2>Requests:</h2>\n");
527         sb.append("Total requests: ").append(getRequests()).append("<br />\n");
528         sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n");
529         sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n");
530         sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n");
531         sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
532         sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
533         sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
534 
535 
536         sb.append("<h2>Dispatches:</h2>\n");
537         sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
538         sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
539         sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n");
540         sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n");
541         sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n");
542         sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
543         sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
544 
545 
546         sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
547         sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
548         sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
549 
550         sb.append("<h2>Responses:</h2>\n");
551         sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
552         sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
553         sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n");
554         sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n");
555         sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n");
556         sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
557 
558         return sb.toString();
559 
560     }
561 
562     @Override
563     public Future<Void> shutdown()
564     {
565         FutureCallback shutdown=new FutureCallback(false);
566         _shutdown.compareAndSet(null,shutdown);
567         shutdown=_shutdown.get();
568         if (_dispatchedStats.getCurrent()==0)
569             shutdown.succeeded();
570         return shutdown;
571     }
572 }