1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.mina.filter.statistic;
21
22 import java.util.HashSet;
23 import java.util.Set;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicLong;
26
27 import org.apache.mina.core.filterchain.IoFilterAdapter;
28 import org.apache.mina.core.session.IdleStatus;
29 import org.apache.mina.core.session.IoEventType;
30 import org.apache.mina.core.session.IoSession;
31 import org.apache.mina.core.write.WriteRequest;
32
33 /**
34 * This class will measure the time it takes for a
35 * method in the {@link IoFilterAdapter} class to execute. The basic
36 * premise of the logic in this class is to get the current time
37 * at the beginning of the method, call method on nextFilter, and
38 * then get the current time again. An example of how to use
39 * the filter is:
40 *
41 * <pre>
42 * ProfilerTimerFilter profiler = new ProfilerTimerFilter(
43 * TimeUnit.MILLISECOND, IoEventType.MESSAGE_RECEIVED);
44 * chain.addFirst("Profiler", profiler);
45 * </pre>
46 *
47 * The profiled {@link IoEventType} are :
48 * <ul>
49 * <li>IoEventType.MESSAGE_RECEIVED</li>
50 * <li>IoEventType.MESSAGE_SENT</li>
51 * <li>IoEventType.SESSION_CREATED</li>
52 * <li>IoEventType.SESSION_OPENED</li>
53 * <li>IoEventType.SESSION_IDLE</li>
54 * <li>IoEventType.SESSION_CLOSED</li>
55 * </ul>
56 *
57 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
58 * @org.apache.xbean.XBean
59 */
60 public class ProfilerTimerFilter extends IoFilterAdapter {
61 /** TRhe selected time unit */
62 private volatile TimeUnit timeUnit;
63
64 /** A TimerWorker for the MessageReceived events */
65 private TimerWorker messageReceivedTimerWorker;
66
67 /** A flag to tell the filter that the MessageReceived must be profiled */
68 private boolean profileMessageReceived = false;
69
70 /** A TimerWorker for the MessageSent events */
71 private TimerWorker messageSentTimerWorker;
72
73 /** A flag to tell the filter that the MessageSent must be profiled */
74 private boolean profileMessageSent = false;
75
76 /** A TimerWorker for the SessionCreated events */
77 private TimerWorker sessionCreatedTimerWorker;
78
79 /** A flag to tell the filter that the SessionCreated must be profiled */
80 private boolean profileSessionCreated = false;
81
82 /** A TimerWorker for the SessionOpened events */
83 private TimerWorker sessionOpenedTimerWorker;
84
85 /** A flag to tell the filter that the SessionOpened must be profiled */
86 private boolean profileSessionOpened = false;
87
88 /** A TimerWorker for the SessionIdle events */
89 private TimerWorker sessionIdleTimerWorker;
90
91 /** A flag to tell the filter that the SessionIdle must be profiled */
92 private boolean profileSessionIdle = false;
93
94 /** A TimerWorker for the SessionClosed events */
95 private TimerWorker sessionClosedTimerWorker;
96
97 /** A flag to tell the filter that the SessionClosed must be profiled */
98 private boolean profileSessionClosed = false;
99
100 /**
101 * Creates a new instance of ProfilerFilter. This is the
102 * default constructor and will print out timings for
103 * messageReceived and messageSent and the time increment
104 * will be in milliseconds.
105 */
106 public ProfilerTimerFilter() {
107 this(
108 TimeUnit.MILLISECONDS,
109 IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
110 }
111
112 /**
113 * Creates a new instance of ProfilerFilter. This is the
114 * default constructor and will print out timings for
115 * messageReceived and messageSent.
116 *
117 * @param timeUnit the time increment to set
118 */
119 public ProfilerTimerFilter(TimeUnit timeUnit) {
120 this(
121 timeUnit,
122 IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
123 }
124
125 /**
126 * Creates a new instance of ProfilerFilter. An example
127 * of this call would be:
128 *
129 * <pre>
130 * new ProfilerTimerFilter(
131 * TimeUnit.MILLISECONDS,
132 * IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
133 * </pre>
134 *
135 * Note : you can add as many {@link IoEventType} as you want. The method accepts
136 * a variable number of arguments.
137 *
138 * @param timeUnit Used to determine the level of precision you need in your timing.
139 * @param eventTypes A list of {@link IoEventType} representation of the methods to profile
140 */
141 public ProfilerTimerFilter(TimeUnit timeUnit, IoEventType... eventTypes) {
142 this.timeUnit = timeUnit;
143
144 setProfilers(eventTypes);
145 }
146
147 /**
148 * Create the profilers for a list of {@link IoEventType}.
149 *
150 * @param eventTypes the list of {@link IoEventType} to profile
151 */
152 private void setProfilers(IoEventType... eventTypes) {
153 for (IoEventType type : eventTypes) {
154 switch (type) {
155 case MESSAGE_RECEIVED :
156 messageReceivedTimerWorker = new TimerWorker();
157 profileMessageReceived = true;
158 break;
159
160 case MESSAGE_SENT :
161 messageSentTimerWorker = new TimerWorker();
162 profileMessageSent = true;
163 break;
164
165 case SESSION_CREATED :
166 sessionCreatedTimerWorker = new TimerWorker();
167 profileSessionCreated = true;
168 break;
169
170 case SESSION_OPENED :
171 sessionOpenedTimerWorker = new TimerWorker();
172 profileSessionOpened = true;
173 break;
174
175 case SESSION_IDLE :
176 sessionIdleTimerWorker = new TimerWorker();
177 profileSessionIdle = true;
178 break;
179
180 case SESSION_CLOSED :
181 sessionClosedTimerWorker = new TimerWorker();
182 profileSessionClosed = true;
183 break;
184 }
185 }
186 }
187
188 /**
189 * Sets the {@link TimeUnit} being used.
190 *
191 * @param timeUnit the new {@link TimeUnit} to be used.
192 */
193 public void setTimeUnit(TimeUnit timeUnit) {
194 this.timeUnit = timeUnit;
195 }
196
197 /**
198 * Set the {@link IoEventType} to be profiled
199 *
200 * @param type The {@link IoEventType} to profile
201 */
202 public void profile(IoEventType type) {
203 switch (type) {
204 case MESSAGE_RECEIVED :
205 profileMessageReceived = true;
206
207 if (messageReceivedTimerWorker == null) {
208 messageReceivedTimerWorker = new TimerWorker();
209 }
210
211 return;
212
213 case MESSAGE_SENT :
214 profileMessageSent = true;
215
216 if (messageSentTimerWorker == null) {
217 messageSentTimerWorker = new TimerWorker();
218 }
219
220 return;
221
222 case SESSION_CREATED :
223 profileSessionCreated = true;
224
225 if (sessionCreatedTimerWorker == null) {
226 sessionCreatedTimerWorker = new TimerWorker();
227 }
228
229 return;
230
231 case SESSION_OPENED :
232 profileSessionOpened = true;
233
234 if (sessionOpenedTimerWorker == null) {
235 sessionOpenedTimerWorker = new TimerWorker();
236 }
237
238 return;
239
240 case SESSION_IDLE :
241 profileSessionIdle = true;
242
243 if (sessionIdleTimerWorker == null) {
244 sessionIdleTimerWorker = new TimerWorker();
245 }
246
247 return;
248
249 case SESSION_CLOSED :
250 profileSessionClosed = true;
251
252 if (sessionClosedTimerWorker == null) {
253 sessionClosedTimerWorker = new TimerWorker();
254 }
255
256 return;
257 }
258 }
259
260 /**
261 * Stop profiling an {@link IoEventType}
262 *
263 * @param type The {@link IoEventType} to stop profiling
264 */
265 public void stopProfile(IoEventType type) {
266 switch (type) {
267 case MESSAGE_RECEIVED :
268 profileMessageReceived = false;
269 return;
270
271 case MESSAGE_SENT :
272 profileMessageSent = false;
273 return;
274
275 case SESSION_CREATED :
276 profileSessionCreated = false;
277 return;
278
279 case SESSION_OPENED :
280 profileSessionOpened = false;
281 return;
282
283 case SESSION_IDLE :
284 profileSessionIdle = false;
285 return;
286
287 case SESSION_CLOSED :
288 profileSessionClosed = false;
289 return;
290 }
291 }
292
293 /**
294 * Return the set of {@link IoEventType} which are profiled.
295 *
296 * @return a Set containing all the profiled {@link IoEventType}
297 */
298 public Set<IoEventType> getEventsToProfile() {
299 Set<IoEventType> set = new HashSet<IoEventType>();
300
301 if ( profileMessageReceived ) {
302 set.add(IoEventType.MESSAGE_RECEIVED);
303 }
304
305 if ( profileMessageSent) {
306 set.add(IoEventType.MESSAGE_SENT);
307 }
308
309 if ( profileSessionCreated ) {
310 set.add(IoEventType.SESSION_CREATED);
311 }
312
313 if ( profileSessionOpened ) {
314 set.add(IoEventType.SESSION_OPENED);
315 }
316
317 if ( profileSessionIdle ) {
318 set.add(IoEventType.SESSION_IDLE);
319 }
320
321 if ( profileSessionClosed ) {
322 set.add(IoEventType.SESSION_CLOSED);
323 }
324
325 return set;
326 }
327
328 /**
329 * Set the profilers for a list of {@link IoEventType}
330 *
331 * @param eventTypes the list of {@link IoEventType} to profile
332 */
333 public void setEventsToProfile(IoEventType... eventTypes) {
334 setProfilers(eventTypes);
335 }
336
337 /**
338 * Profile a MessageReceived event. This method will gather the following
339 * informations :
340 * - the method duration
341 * - the shortest execution time
342 * - the slowest execution time
343 * - the average execution time
344 * - the global number of calls
345 *
346 * @param nextFilter The filter to call next
347 * @param session The associated session
348 * @param message the received message
349 */
350 @Override
351 public void messageReceived(NextFilter nextFilter, IoSession session,
352 Object message) throws Exception {
353 if (profileMessageReceived) {
354 long start = timeNow();
355 nextFilter.messageReceived(session, message);
356 long end = timeNow();
357 messageReceivedTimerWorker.addNewDuration(end - start);
358 } else {
359 nextFilter.messageReceived(session, message);
360 }
361 }
362
363 /**
364 * Profile a MessageSent event. This method will gather the following
365 * informations :
366 * - the method duration
367 * - the shortest execution time
368 * - the slowest execution time
369 * - the average execution time
370 * - the global number of calls
371 *
372 * @param nextFilter The filter to call next
373 * @param session The associated session
374 * @param writeRequest the sent message
375 */
376 @Override
377 public void messageSent(NextFilter nextFilter, IoSession session,
378 WriteRequest writeRequest) throws Exception {
379 if (profileMessageSent) {
380 long start = timeNow();
381 nextFilter.messageSent(session, writeRequest);
382 long end = timeNow();
383 messageSentTimerWorker.addNewDuration(end - start);
384 } else {
385 nextFilter.messageSent(session, writeRequest);
386 }
387 }
388
389 /**
390 * Profile a SessionCreated event. This method will gather the following
391 * informations :
392 * - the method duration
393 * - the shortest execution time
394 * - the slowest execution time
395 * - the average execution time
396 * - the global number of calls
397 *
398 * @param nextFilter The filter to call next
399 * @param session The associated session
400 */
401 @Override
402 public void sessionCreated(NextFilter nextFilter, IoSession session)
403 throws Exception {
404 if (profileSessionCreated) {
405 long start = timeNow();
406 nextFilter.sessionCreated(session);
407 long end = timeNow();
408 sessionCreatedTimerWorker.addNewDuration(end - start);
409 } else {
410 nextFilter.sessionCreated(session);
411 }
412 }
413
414 /**
415 * Profile a SessionOpened event. This method will gather the following
416 * informations :
417 * - the method duration
418 * - the shortest execution time
419 * - the slowest execution time
420 * - the average execution time
421 * - the global number of calls
422 *
423 * @param nextFilter The filter to call next
424 * @param session The associated session
425 */
426 @Override
427 public void sessionOpened(NextFilter nextFilter, IoSession session)
428 throws Exception {
429 if (profileSessionOpened) {
430 long start = timeNow();
431 nextFilter.sessionOpened(session);
432 long end = timeNow();
433 sessionOpenedTimerWorker.addNewDuration(end - start);
434 } else {
435 nextFilter.sessionOpened(session);
436 }
437 }
438
439 /**
440 * Profile a SessionIdle event. This method will gather the following
441 * informations :
442 * - the method duration
443 * - the shortest execution time
444 * - the slowest execution time
445 * - the average execution time
446 * - the global number of calls
447 *
448 * @param nextFilter The filter to call next
449 * @param session The associated session
450 * @param status The session's status
451 */
452 @Override
453 public void sessionIdle(NextFilter nextFilter, IoSession session,
454 IdleStatus status) throws Exception {
455 if (profileSessionIdle) {
456 long start = timeNow();
457 nextFilter.sessionIdle(session, status);
458 long end = timeNow();
459 sessionIdleTimerWorker.addNewDuration(end - start);
460 } else {
461 nextFilter.sessionIdle(session, status);
462 }
463 }
464
465 /**
466 * Profile a SessionClosed event. This method will gather the following
467 * informations :
468 * - the method duration
469 * - the shortest execution time
470 * - the slowest execution time
471 * - the average execution time
472 * - the global number of calls
473 *
474 * @param nextFilter The filter to call next
475 * @param session The associated session
476 */
477 @Override
478 public void sessionClosed(NextFilter nextFilter, IoSession session)
479 throws Exception {
480 if (profileSessionClosed) {
481 long start = timeNow();
482 nextFilter.sessionClosed(session);
483 long end = timeNow();
484 sessionClosedTimerWorker.addNewDuration(end - start);
485 } else {
486 nextFilter.sessionClosed(session);
487 }
488 }
489
490 /**
491 * Get the average time for the specified method represented by the {@link IoEventType}
492 *
493 * @param type
494 * The {@link IoEventType} that the user wants to get the average method call time
495 * @return
496 * The average time it took to execute the method represented by the {@link IoEventType}
497 */
498 public double getAverageTime(IoEventType type) {
499 switch (type) {
500 case MESSAGE_RECEIVED :
501 if (profileMessageReceived) {
502 return messageReceivedTimerWorker.getAverage();
503 }
504
505 break;
506
507 case MESSAGE_SENT :
508 if (profileMessageSent) {
509 return messageSentTimerWorker.getAverage();
510 }
511
512 break;
513
514 case SESSION_CREATED :
515 if (profileSessionCreated) {
516 return sessionCreatedTimerWorker.getAverage();
517 }
518
519 break;
520
521 case SESSION_OPENED :
522 if (profileSessionOpened) {
523 return sessionOpenedTimerWorker.getAverage();
524 }
525
526 break;
527
528 case SESSION_IDLE :
529 if (profileSessionIdle) {
530 return sessionIdleTimerWorker.getAverage();
531 }
532
533 break;
534
535 case SESSION_CLOSED :
536 if (profileSessionClosed) {
537 return sessionClosedTimerWorker.getAverage();
538 }
539
540 break;
541 }
542
543 throw new IllegalArgumentException(
544 "You are not monitoring this event. Please add this event first.");
545 }
546
547 /**
548 * Gets the total number of times the method has been called that is represented by the
549 * {@link IoEventType}
550 *
551 * @param type
552 * The {@link IoEventType} that the user wants to get the total number of method calls
553 * @return
554 * The total number of method calls for the method represented by the {@link IoEventType}
555 */
556 public long getTotalCalls(IoEventType type) {
557 switch (type) {
558 case MESSAGE_RECEIVED :
559 if (profileMessageReceived) {
560 return messageReceivedTimerWorker.getCallsNumber();
561 }
562
563 break;
564
565 case MESSAGE_SENT :
566 if (profileMessageSent) {
567 return messageSentTimerWorker.getCallsNumber();
568 }
569
570 break;
571
572 case SESSION_CREATED :
573 if (profileSessionCreated) {
574 return sessionCreatedTimerWorker.getCallsNumber();
575 }
576
577 break;
578
579 case SESSION_OPENED :
580 if (profileSessionOpened) {
581 return sessionOpenedTimerWorker.getCallsNumber();
582 }
583
584 break;
585
586 case SESSION_IDLE :
587 if (profileSessionIdle) {
588 return sessionIdleTimerWorker.getCallsNumber();
589 }
590
591 break;
592
593 case SESSION_CLOSED :
594 if (profileSessionClosed) {
595 return sessionClosedTimerWorker.getCallsNumber();
596 }
597
598 break;
599 }
600
601 throw new IllegalArgumentException(
602 "You are not monitoring this event. Please add this event first.");
603 }
604
605 /**
606 * The total time this method has been executing
607 *
608 * @param type
609 * The {@link IoEventType} that the user wants to get the total time this method has
610 * been executing
611 * @return
612 * The total time for the method represented by the {@link IoEventType}
613 */
614 public long getTotalTime(IoEventType type) {
615 switch (type) {
616 case MESSAGE_RECEIVED :
617 if (profileMessageReceived) {
618 return messageReceivedTimerWorker.getTotal();
619 }
620
621 break;
622
623 case MESSAGE_SENT :
624 if (profileMessageSent) {
625 return messageSentTimerWorker.getTotal();
626 }
627
628 break;
629
630 case SESSION_CREATED :
631 if (profileSessionCreated) {
632 return sessionCreatedTimerWorker.getTotal();
633 }
634
635 break;
636
637 case SESSION_OPENED :
638 if (profileSessionOpened) {
639 return sessionOpenedTimerWorker.getTotal();
640 }
641
642 break;
643
644 case SESSION_IDLE :
645 if (profileSessionIdle) {
646 return sessionIdleTimerWorker.getTotal();
647 }
648
649 break;
650
651 case SESSION_CLOSED :
652 if (profileSessionClosed) {
653 return sessionClosedTimerWorker.getTotal();
654 }
655
656 break;
657 }
658
659 throw new IllegalArgumentException(
660 "You are not monitoring this event. Please add this event first.");
661 }
662
663 /**
664 * The minimum time the method represented by {@link IoEventType} has executed
665 *
666 * @param type
667 * The {@link IoEventType} that the user wants to get the minimum time this method has
668 * executed
669 * @return
670 * The minimum time this method has executed represented by the {@link IoEventType}
671 */
672 public long getMinimumTime(IoEventType type) {
673 switch (type) {
674 case MESSAGE_RECEIVED :
675 if (profileMessageReceived) {
676 return messageReceivedTimerWorker.getMinimum();
677 }
678
679 break;
680
681 case MESSAGE_SENT :
682 if (profileMessageSent) {
683 return messageSentTimerWorker.getMinimum();
684 }
685
686 break;
687
688 case SESSION_CREATED :
689 if (profileSessionCreated) {
690 return sessionCreatedTimerWorker.getMinimum();
691 }
692
693 break;
694
695 case SESSION_OPENED :
696 if (profileSessionOpened) {
697 return sessionOpenedTimerWorker.getMinimum();
698 }
699
700 break;
701
702 case SESSION_IDLE :
703 if (profileSessionIdle) {
704 return sessionIdleTimerWorker.getMinimum();
705 }
706
707 break;
708
709 case SESSION_CLOSED :
710 if (profileSessionClosed) {
711 return sessionClosedTimerWorker.getMinimum();
712 }
713
714 break;
715 }
716
717 throw new IllegalArgumentException(
718 "You are not monitoring this event. Please add this event first.");
719 }
720
721 /**
722 * The maximum time the method represented by {@link IoEventType} has executed
723 *
724 * @param type
725 * The {@link IoEventType} that the user wants to get the maximum time this method has
726 * executed
727 * @return
728 * The maximum time this method has executed represented by the {@link IoEventType}
729 */
730 public long getMaximumTime(IoEventType type) {
731 switch (type) {
732 case MESSAGE_RECEIVED :
733 if (profileMessageReceived) {
734 return messageReceivedTimerWorker.getMaximum();
735 }
736
737 break;
738
739 case MESSAGE_SENT :
740 if (profileMessageSent) {
741 return messageSentTimerWorker.getMaximum();
742 }
743
744 break;
745
746 case SESSION_CREATED :
747 if (profileSessionCreated) {
748 return sessionCreatedTimerWorker.getMaximum();
749 }
750
751 break;
752
753 case SESSION_OPENED :
754 if (profileSessionOpened) {
755 return sessionOpenedTimerWorker.getMaximum();
756 }
757
758 break;
759
760 case SESSION_IDLE :
761 if (profileSessionIdle) {
762 return sessionIdleTimerWorker.getMaximum();
763 }
764
765 break;
766
767 case SESSION_CLOSED :
768 if (profileSessionClosed) {
769 return sessionClosedTimerWorker.getMaximum();
770 }
771
772 break;
773 }
774
775 throw new IllegalArgumentException(
776 "You are not monitoring this event. Please add this event first.");
777 }
778
779 /**
780 * Class that will track the time each method takes and be able to provide information
781 * for each method.
782 *
783 */
784 private class TimerWorker {
785 /** The sum of all operation durations */
786 private final AtomicLong total;
787
788 /** The number of calls */
789 private final AtomicLong callsNumber;
790
791 /** The fastest operation */
792 private final AtomicLong minimum;
793
794 /** The slowest operation */
795 private final AtomicLong maximum;
796
797 /** A lock for synchinized blocks */
798 private final Object lock = new Object();
799
800 /**
801 * Creates a new instance of TimerWorker.
802 *
803 */
804 public TimerWorker() {
805 total = new AtomicLong();
806 callsNumber = new AtomicLong();
807 minimum = new AtomicLong();
808 maximum = new AtomicLong();
809 }
810
811 /**
812 * Add a new operation duration to this class. Total is updated
813 * and calls is incremented
814 *
815 * @param duration
816 * The new operation duration
817 */
818 public void addNewDuration(long duration) {
819 callsNumber.incrementAndGet();
820 total.addAndGet(duration);
821
822 synchronized (lock) {
823 // this is not entirely thread-safe, must lock
824 if (duration < minimum.longValue()) {
825 minimum.set(duration);
826 }
827
828 // this is not entirely thread-safe, must lock
829 if (duration > maximum.longValue()) {
830 maximum.set(duration);
831 }
832 }
833 }
834
835 /**
836 * Gets the average reading for this event
837 *
838 * @return the average reading for this event
839 */
840 public double getAverage() {
841 synchronized (lock) {
842 // There are two operations, we need to synchronize the block
843 return total.longValue() / callsNumber.longValue();
844 }
845 }
846
847 /**
848 * Returns the total number of profiled operations
849 *
850 * @return The total number of profiled operation
851 */
852 public long getCallsNumber() {
853 return callsNumber.longValue();
854 }
855
856 /**
857 * Returns the total time
858 *
859 * @return the total time
860 */
861 public long getTotal() {
862 return total.longValue();
863 }
864
865 /**
866 * Returns the lowest execution time
867 *
868 * @return the lowest execution time
869 */
870 public long getMinimum() {
871 return minimum.longValue();
872 }
873
874 /**
875 * Returns the longest execution time
876 *
877 * @return the longest execution time
878 */
879 public long getMaximum() {
880 return maximum.longValue();
881 }
882 }
883
884 /**
885 * @return the current time, expressed using the fixed TimeUnit.
886 */
887 private long timeNow() {
888 switch (timeUnit) {
889 case SECONDS :
890 return System.currentTimeMillis()/1000;
891
892 case MICROSECONDS :
893 return System.nanoTime()/1000;
894
895 case NANOSECONDS :
896 return System.nanoTime();
897
898 default :
899 return System.currentTimeMillis();
900 }
901 }
902 }