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 case SESSION_OPENED :
230 profileSessionOpened = true;
231
232 if (sessionOpenedTimerWorker == null) {
233 sessionOpenedTimerWorker = new TimerWorker();
234 }
235
236 case SESSION_IDLE :
237 profileSessionIdle = true;
238
239 if (sessionIdleTimerWorker == null) {
240 sessionIdleTimerWorker = new TimerWorker();
241 }
242
243 case SESSION_CLOSED :
244 profileSessionClosed = true;
245
246 if (sessionClosedTimerWorker == null) {
247 sessionClosedTimerWorker = new TimerWorker();
248 }
249 }
250 }
251
252 /**
253 * Stop profiling an {@link IoEventType}
254 *
255 * @param type The {@link IoEventType} to stop profiling
256 */
257 public void stopProfile(IoEventType type) {
258 switch (type) {
259 case MESSAGE_RECEIVED :
260 profileMessageReceived = false;
261 return;
262
263 case MESSAGE_SENT :
264 profileMessageSent = false;
265 return;
266
267 case SESSION_CREATED :
268 profileSessionCreated = false;
269 return;
270
271 case SESSION_OPENED :
272 profileSessionOpened = false;
273 return;
274
275 case SESSION_IDLE :
276 profileSessionIdle = false;
277 return;
278
279 case SESSION_CLOSED :
280 profileSessionClosed = false;
281 return;
282 }
283 }
284
285 /**
286 * Return the set of {@link IoEventType} which are profiled.
287 *
288 * @return a Set containing all the profiled {@link IoEventType}
289 */
290 public Set<IoEventType> getEventsToProfile() {
291 Set<IoEventType> set = new HashSet<IoEventType>();
292
293 if ( profileMessageReceived ) {
294 set.add(IoEventType.MESSAGE_RECEIVED);
295 }
296
297 if ( profileMessageSent) {
298 set.add(IoEventType.MESSAGE_SENT);
299 }
300
301 if ( profileSessionCreated ) {
302 set.add(IoEventType.SESSION_CREATED);
303 }
304
305 if ( profileSessionOpened ) {
306 set.add(IoEventType.SESSION_OPENED);
307 }
308
309 if ( profileSessionIdle ) {
310 set.add(IoEventType.SESSION_IDLE);
311 }
312
313 if ( profileSessionClosed ) {
314 set.add(IoEventType.SESSION_CLOSED);
315 }
316
317 return set;
318 }
319
320 /**
321 * Set the profilers for a list of {@link IoEventType}
322 *
323 * @param eventTypes the list of {@link IoEventType} to profile
324 */
325 public void setEventsToProfile(IoEventType... eventTypes) {
326 setProfilers(eventTypes);
327 }
328
329 /**
330 * Profile a MessageReceived event. This method will gather the following
331 * informations :
332 * - the method duration
333 * - the shortest execution time
334 * - the slowest execution time
335 * - the average execution time
336 * - the global number of calls
337 *
338 * @param nextFilter The filter to call next
339 * @param session The associated session
340 * @param message the received message
341 */
342 @Override
343 public void messageReceived(NextFilter nextFilter, IoSession session,
344 Object message) throws Exception {
345 if (profileMessageReceived) {
346 long start = timeNow();
347 nextFilter.messageReceived(session, message);
348 long end = timeNow();
349 messageReceivedTimerWorker.addNewDuration(end - start);
350 } else {
351 nextFilter.messageReceived(session, message);
352 }
353 }
354
355 /**
356 * Profile a MessageSent event. This method will gather the following
357 * informations :
358 * - the method duration
359 * - the shortest execution time
360 * - the slowest execution time
361 * - the average execution time
362 * - the global number of calls
363 *
364 * @param nextFilter The filter to call next
365 * @param session The associated session
366 * @param writeRequest the sent message
367 */
368 @Override
369 public void messageSent(NextFilter nextFilter, IoSession session,
370 WriteRequest writeRequest) throws Exception {
371 if (profileMessageSent) {
372 long start = timeNow();
373 nextFilter.messageSent(session, writeRequest);
374 long end = timeNow();
375 messageSentTimerWorker.addNewDuration(end - start);
376 } else {
377 nextFilter.messageSent(session, writeRequest);
378 }
379 }
380
381 /**
382 * Profile a SessionCreated event. This method will gather the following
383 * informations :
384 * - the method duration
385 * - the shortest execution time
386 * - the slowest execution time
387 * - the average execution time
388 * - the global number of calls
389 *
390 * @param nextFilter The filter to call next
391 * @param session The associated session
392 */
393 @Override
394 public void sessionCreated(NextFilter nextFilter, IoSession session)
395 throws Exception {
396 if (profileSessionCreated) {
397 long start = timeNow();
398 nextFilter.sessionCreated(session);
399 long end = timeNow();
400 sessionCreatedTimerWorker.addNewDuration(end - start);
401 } else {
402 nextFilter.sessionCreated(session);
403 }
404 }
405
406 /**
407 * Profile a SessionOpened event. This method will gather the following
408 * informations :
409 * - the method duration
410 * - the shortest execution time
411 * - the slowest execution time
412 * - the average execution time
413 * - the global number of calls
414 *
415 * @param nextFilter The filter to call next
416 * @param session The associated session
417 */
418 @Override
419 public void sessionOpened(NextFilter nextFilter, IoSession session)
420 throws Exception {
421 if (profileSessionOpened) {
422 long start = timeNow();
423 nextFilter.sessionOpened(session);
424 long end = timeNow();
425 sessionOpenedTimerWorker.addNewDuration(end - start);
426 } else {
427 nextFilter.sessionOpened(session);
428 }
429 }
430
431 /**
432 * Profile a SessionIdle event. This method will gather the following
433 * informations :
434 * - the method duration
435 * - the shortest execution time
436 * - the slowest execution time
437 * - the average execution time
438 * - the global number of calls
439 *
440 * @param nextFilter The filter to call next
441 * @param session The associated session
442 * @param status The session's status
443 */
444 @Override
445 public void sessionIdle(NextFilter nextFilter, IoSession session,
446 IdleStatus status) throws Exception {
447 if (profileSessionIdle) {
448 long start = timeNow();
449 nextFilter.sessionIdle(session, status);
450 long end = timeNow();
451 sessionIdleTimerWorker.addNewDuration(end - start);
452 } else {
453 nextFilter.sessionIdle(session, status);
454 }
455 }
456
457 /**
458 * Profile a SessionClosed event. This method will gather the following
459 * informations :
460 * - the method duration
461 * - the shortest execution time
462 * - the slowest execution time
463 * - the average execution time
464 * - the global number of calls
465 *
466 * @param nextFilter The filter to call next
467 * @param session The associated session
468 */
469 @Override
470 public void sessionClosed(NextFilter nextFilter, IoSession session)
471 throws Exception {
472 if (profileSessionClosed) {
473 long start = timeNow();
474 nextFilter.sessionClosed(session);
475 long end = timeNow();
476 sessionClosedTimerWorker.addNewDuration(end - start);
477 } else {
478 nextFilter.sessionClosed(session);
479 }
480 }
481
482 /**
483 * Get the average time for the specified method represented by the {@link IoEventType}
484 *
485 * @param type
486 * The {@link IoEventType} that the user wants to get the average method call time
487 * @return
488 * The average time it took to execute the method represented by the {@link IoEventType}
489 */
490 public double getAverageTime(IoEventType type) {
491 switch (type) {
492 case MESSAGE_RECEIVED :
493 if (profileMessageReceived) {
494 return messageReceivedTimerWorker.getAverage();
495 }
496
497 break;
498
499 case MESSAGE_SENT :
500 if (profileMessageSent) {
501 return messageSentTimerWorker.getAverage();
502 }
503
504 break;
505
506 case SESSION_CREATED :
507 if (profileSessionCreated) {
508 return sessionCreatedTimerWorker.getAverage();
509 }
510
511 break;
512
513 case SESSION_OPENED :
514 if (profileSessionOpened) {
515 return sessionOpenedTimerWorker.getAverage();
516 }
517
518 break;
519
520 case SESSION_IDLE :
521 if (profileSessionIdle) {
522 return sessionIdleTimerWorker.getAverage();
523 }
524
525 break;
526
527 case SESSION_CLOSED :
528 if (profileSessionClosed) {
529 return sessionClosedTimerWorker.getAverage();
530 }
531
532 break;
533 }
534
535 throw new IllegalArgumentException(
536 "You are not monitoring this event. Please add this event first.");
537 }
538
539 /**
540 * Gets the total number of times the method has been called that is represented by the
541 * {@link IoEventType}
542 *
543 * @param type
544 * The {@link IoEventType} that the user wants to get the total number of method calls
545 * @return
546 * The total number of method calls for the method represented by the {@link IoEventType}
547 */
548 public long getTotalCalls(IoEventType type) {
549 switch (type) {
550 case MESSAGE_RECEIVED :
551 if (profileMessageReceived) {
552 return messageReceivedTimerWorker.getCallsNumber();
553 }
554
555 break;
556
557 case MESSAGE_SENT :
558 if (profileMessageSent) {
559 return messageSentTimerWorker.getCallsNumber();
560 }
561
562 break;
563
564 case SESSION_CREATED :
565 if (profileSessionCreated) {
566 return sessionCreatedTimerWorker.getCallsNumber();
567 }
568
569 break;
570
571 case SESSION_OPENED :
572 if (profileSessionOpened) {
573 return sessionOpenedTimerWorker.getCallsNumber();
574 }
575
576 break;
577
578 case SESSION_IDLE :
579 if (profileSessionIdle) {
580 return sessionIdleTimerWorker.getCallsNumber();
581 }
582
583 break;
584
585 case SESSION_CLOSED :
586 if (profileSessionClosed) {
587 return sessionClosedTimerWorker.getCallsNumber();
588 }
589
590 break;
591 }
592
593 throw new IllegalArgumentException(
594 "You are not monitoring this event. Please add this event first.");
595 }
596
597 /**
598 * The total time this method has been executing
599 *
600 * @param type
601 * The {@link IoEventType} that the user wants to get the total time this method has
602 * been executing
603 * @return
604 * The total time for the method represented by the {@link IoEventType}
605 */
606 public long getTotalTime(IoEventType type) {
607 switch (type) {
608 case MESSAGE_RECEIVED :
609 if (profileMessageReceived) {
610 return messageReceivedTimerWorker.getTotal();
611 }
612
613 break;
614
615 case MESSAGE_SENT :
616 if (profileMessageSent) {
617 return messageSentTimerWorker.getTotal();
618 }
619
620 break;
621
622 case SESSION_CREATED :
623 if (profileSessionCreated) {
624 return sessionCreatedTimerWorker.getTotal();
625 }
626
627 break;
628
629 case SESSION_OPENED :
630 if (profileSessionOpened) {
631 return sessionOpenedTimerWorker.getTotal();
632 }
633
634 break;
635
636 case SESSION_IDLE :
637 if (profileSessionIdle) {
638 return sessionIdleTimerWorker.getTotal();
639 }
640
641 break;
642
643 case SESSION_CLOSED :
644 if (profileSessionClosed) {
645 return sessionClosedTimerWorker.getTotal();
646 }
647
648 break;
649 }
650
651 throw new IllegalArgumentException(
652 "You are not monitoring this event. Please add this event first.");
653 }
654
655 /**
656 * The minimum time the method represented by {@link IoEventType} has executed
657 *
658 * @param type
659 * The {@link IoEventType} that the user wants to get the minimum time this method has
660 * executed
661 * @return
662 * The minimum time this method has executed represented by the {@link IoEventType}
663 */
664 public long getMinimumTime(IoEventType type) {
665 switch (type) {
666 case MESSAGE_RECEIVED :
667 if (profileMessageReceived) {
668 return messageReceivedTimerWorker.getMinimum();
669 }
670
671 break;
672
673 case MESSAGE_SENT :
674 if (profileMessageSent) {
675 return messageSentTimerWorker.getMinimum();
676 }
677
678 break;
679
680 case SESSION_CREATED :
681 if (profileSessionCreated) {
682 return sessionCreatedTimerWorker.getMinimum();
683 }
684
685 break;
686
687 case SESSION_OPENED :
688 if (profileSessionOpened) {
689 return sessionOpenedTimerWorker.getMinimum();
690 }
691
692 break;
693
694 case SESSION_IDLE :
695 if (profileSessionIdle) {
696 return sessionIdleTimerWorker.getMinimum();
697 }
698
699 break;
700
701 case SESSION_CLOSED :
702 if (profileSessionClosed) {
703 return sessionClosedTimerWorker.getMinimum();
704 }
705
706 break;
707 }
708
709 throw new IllegalArgumentException(
710 "You are not monitoring this event. Please add this event first.");
711 }
712
713 /**
714 * The maximum time the method represented by {@link IoEventType} has executed
715 *
716 * @param type
717 * The {@link IoEventType} that the user wants to get the maximum time this method has
718 * executed
719 * @return
720 * The maximum time this method has executed represented by the {@link IoEventType}
721 */
722 public long getMaximumTime(IoEventType type) {
723 switch (type) {
724 case MESSAGE_RECEIVED :
725 if (profileMessageReceived) {
726 return messageReceivedTimerWorker.getMaximum();
727 }
728
729 break;
730
731 case MESSAGE_SENT :
732 if (profileMessageSent) {
733 return messageSentTimerWorker.getMaximum();
734 }
735
736 break;
737
738 case SESSION_CREATED :
739 if (profileSessionCreated) {
740 return sessionCreatedTimerWorker.getMaximum();
741 }
742
743 break;
744
745 case SESSION_OPENED :
746 if (profileSessionOpened) {
747 return sessionOpenedTimerWorker.getMaximum();
748 }
749
750 break;
751
752 case SESSION_IDLE :
753 if (profileSessionIdle) {
754 return sessionIdleTimerWorker.getMaximum();
755 }
756
757 break;
758
759 case SESSION_CLOSED :
760 if (profileSessionClosed) {
761 return sessionClosedTimerWorker.getMaximum();
762 }
763
764 break;
765 }
766
767 throw new IllegalArgumentException(
768 "You are not monitoring this event. Please add this event first.");
769 }
770
771 /**
772 * Class that will track the time each method takes and be able to provide information
773 * for each method.
774 *
775 */
776 private class TimerWorker {
777 /** The sum of all operation durations */
778 private final AtomicLong total;
779
780 /** The number of calls */
781 private final AtomicLong callsNumber;
782
783 /** The fastest operation */
784 private final AtomicLong minimum;
785
786 /** The slowest operation */
787 private final AtomicLong maximum;
788
789 /** A lock for synchinized blocks */
790 private final Object lock = new Object();
791
792 /**
793 * Creates a new instance of TimerWorker.
794 *
795 */
796 public TimerWorker() {
797 total = new AtomicLong();
798 callsNumber = new AtomicLong();
799 minimum = new AtomicLong();
800 maximum = new AtomicLong();
801 }
802
803 /**
804 * Add a new operation duration to this class. Total is updated
805 * and calls is incremented
806 *
807 * @param duration
808 * The new operation duration
809 */
810 public void addNewDuration(long duration) {
811 callsNumber.incrementAndGet();
812 total.addAndGet(duration);
813
814 synchronized (lock) {
815 // this is not entirely thread-safe, must lock
816 if (duration < minimum.longValue()) {
817 minimum.set(duration);
818 }
819
820 // this is not entirely thread-safe, must lock
821 if (duration > maximum.longValue()) {
822 maximum.set(duration);
823 }
824 }
825 }
826
827 /**
828 * Gets the average reading for this event
829 *
830 * @return the average reading for this event
831 */
832 public double getAverage() {
833 synchronized (lock) {
834 // There are two operations, we need to synchronize the block
835 return total.longValue() / callsNumber.longValue();
836 }
837 }
838
839 /**
840 * Returns the total number of profiled operations
841 *
842 * @return The total number of profiled operation
843 */
844 public long getCallsNumber() {
845 return callsNumber.longValue();
846 }
847
848 /**
849 * Returns the total time
850 *
851 * @return the total time
852 */
853 public long getTotal() {
854 return total.longValue();
855 }
856
857 /**
858 * Returns the lowest execution time
859 *
860 * @return the lowest execution time
861 */
862 public long getMinimum() {
863 return minimum.longValue();
864 }
865
866 /**
867 * Returns the longest execution time
868 *
869 * @return the longest execution time
870 */
871 public long getMaximum() {
872 return maximum.longValue();
873 }
874 }
875
876 /**
877 * @return the current time, expressed using the fixed TimeUnit.
878 */
879 private long timeNow() {
880 switch (timeUnit) {
881 case SECONDS :
882 return System.currentTimeMillis()/1000;
883
884 case MICROSECONDS :
885 return System.nanoTime()/1000;
886
887 case NANOSECONDS :
888 return System.nanoTime();
889
890 default :
891 return System.currentTimeMillis();
892 }
893 }
894 }