001 /*
002 * $Id: ProfileStatistic.java,v 1.24 2014/05/10 21:43:38 oboehm Exp $
003 *
004 * Copyright (c) 2008 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 22.12.2008 by oliver (ob@oasd.de)
019 */
020 package patterntesting.runtime.monitor;
021
022 import java.io.*;
023 import java.lang.management.ManagementFactory;
024 import java.lang.reflect.*;
025 import java.util.*;
026
027 import javax.management.*;
028 import javax.management.openmbean.*;
029
030 import org.aspectj.lang.Signature;
031 import org.slf4j.*;
032
033 import patterntesting.annotation.check.runtime.MayReturnNull;
034 import patterntesting.runtime.annotation.DontProfileMe;
035 import patterntesting.runtime.jmx.MBeanHelper;
036 import patterntesting.runtime.util.*;
037
038 /**
039 * This is constructed as a thin layer around com.jamonapi.MonitorFactory for
040 * the needs of patterntesting. The reason for this layer is that sometimes you
041 * want to minimize the use of other libraries. So this implementation
042 * provides also an implementation if the JaMon library is missing.
043 *
044 * @see com.jamonapi.MonitorFactory
045 * @author <a href="boehm@javatux.de">oliver</a>
046 * @since 22.12.2008
047 * @version $Revision: 1.24 $
048 */
049 public class ProfileStatistic extends Thread implements ProfileStatisticMBean {
050
051 private static final ProfileStatistic instance;
052 private static final Logger log = LoggerFactory.getLogger(ProfileStatistic.class);
053
054 private final ObjectName mbeanName;
055 private final SimpleProfileMonitor rootMonitor;
056
057 /** Is JaMon library available?. */
058 protected static final boolean jamonAvailable;
059
060 /**
061 * ProfileStatistic *must* be initialized after isJamonAvailable attribute
062 * is set. Otherwise you'll get a NullPointerException after MBean
063 * registration.
064 */
065 static {
066 jamonAvailable = Environment.isJamonAvailable();
067 instance = new ProfileStatistic("root");
068 }
069
070 /**
071 * Gets the single instance of ProfileStatistic.
072 *
073 * @return single instance of ProfileStatistic
074 */
075 public static ProfileStatistic getInstance() {
076 return instance;
077 }
078
079 /**
080 * Instantiates a new profile statistic.
081 *
082 * @param rootLabel the root label
083 */
084 protected ProfileStatistic(final String rootLabel) {
085 this.rootMonitor = new SimpleProfileMonitor(rootLabel);
086 this.mbeanName = this.registerAsMBean();
087 }
088
089 private ObjectName registerAsMBean() {
090 ObjectName name = createObjectName();
091 MBeanServer server = ManagementFactory.getPlatformMBeanServer();
092 try {
093 server.registerMBean(this, name);
094 } catch (InstanceAlreadyExistsException ex) {
095 log.info(getMBeanName() + " was already registered.", ex);
096 } catch (MBeanRegistrationException ex) {
097 log.info(getMBeanName() + " cannot be registered.", ex);
098 } catch (NotCompliantMBeanException ex) {
099 log.info(getMBeanName() + " is not a compliant MBean.", ex);
100 }
101 log.info("{} successful registered as MBean", name);
102 return name;
103 }
104
105 private ObjectName createObjectName() {
106 String name = MBeanHelper.getMBeanName(this);
107 ObjectName objectName = null;
108 try {
109 objectName = new ObjectName(name);
110 } catch (MalformedObjectNameException e) {
111 log.info("can't create object name + '" + name + "'");
112 }
113 return objectName;
114 }
115
116 /**
117 * You can register the instance as shutdown hook. If the VM is
118 * terminated the profile values are logged and dumped to a CSV file in the
119 * tmp directory.
120 *
121 * @see #logStatistic()
122 * @see #dumpStatistic()
123 */
124 public static void addAsShutdownHook() {
125 addAsShutdownHook(instance);
126 }
127
128 /**
129 * Adds the given instance (hook) as shutdown hook.
130 *
131 * @param hook the hook
132 */
133 protected static void addAsShutdownHook(final ProfileStatistic hook) {
134 Runtime.getRuntime().addShutdownHook(hook);
135 if (log.isDebugEnabled()) {
136 log.debug(hook + " registered as shutdown hook");
137 }
138 }
139
140 /**
141 * We can't reset all ProfileMonitors - we must keep the empty
142 * monitors with 0 hits to see which methods or constructors are
143 * never called.
144 */
145 public void reset() {
146 synchronized(ProfileStatistic.class) {
147 List<String> labels = new ArrayList<String>();
148 ProfileMonitor[] monitors = getMonitors();
149 for (int i = 0; i < monitors.length; i++) {
150 if (monitors[i].getHits() == 0) {
151 labels.add(monitors[i].getLabel());
152 }
153 }
154 if (jamonAvailable) {
155 JamonMonitorFactory.addMonitors(labels);
156 } else {
157 rootMonitor.addChildren(labels);
158 }
159 }
160 }
161
162 /**
163 * Resets the root monitor.
164 */
165 protected void resetRootMonitor() {
166 if (jamonAvailable) {
167 JamonMonitorFactory.reset(this.rootMonitor);
168 } else {
169 this.rootMonitor.reset();
170 }
171 }
172
173 /**
174 * For each constructor and for each method of the given class a
175 * ProfileMonitor is initialized. This is done to be able to find
176 * constructors and methods which are are never used (i.e. their
177 * hit count is zero).
178 *
179 * @param cl the given class
180 */
181 public void init(final Class<?> cl) {
182 if (log.isTraceEnabled()) {
183 log.trace("initializing monitors for " + cl + "...");
184 }
185 instance.init(cl, cl.getMethods());
186 instance.init(cl.getConstructors());
187 }
188
189 /**
190 * Only methods of the given class but not the methods of the superclass
191 * are initialized for profiling.
192 *
193 * @param cl
194 * @param methods
195 */
196 private void init(final Class<?> cl, final Method[] methods) {
197 for (int i = 0; i < methods.length; i++) {
198 Class<?> declaring = methods[i].getDeclaringClass();
199 if (cl.equals(declaring)) {
200 init(methods[i]);
201 } else if (log.isTraceEnabled()) {
202 log.trace(methods[i] + " not defined in " + cl
203 + " -> no monitor initialized");
204 }
205 }
206 }
207
208 private void init(final Method method) {
209 if (method.getAnnotation(DontProfileMe.class) != null) {
210 if (log.isTraceEnabled()) {
211 log.trace("@DontProfileMe " + method + " is ignored");
212 }
213 return;
214 }
215 Signature sig = SignatureHelper.getAsSignature(method);
216 ProfileMonitor mon = getMonitor(sig);
217 if (log.isTraceEnabled()) {
218 log.trace(mon + " initialized");
219 }
220 }
221
222 private void init(final Constructor<?>[] ctors) {
223 for (int i = 0; i < ctors.length; i++) {
224 init(ctors[i]);
225 }
226 }
227
228 private void init(final Constructor<?> ctor) {
229 if (ctor.getAnnotation(DontProfileMe.class) != null) {
230 if (log.isTraceEnabled()) {
231 log.trace("@DontProfileMe " + ctor + " is ignored");
232 }
233 return;
234 }
235 Signature sig = SignatureHelper.getAsSignature(ctor);
236 ProfileMonitor mon = getMonitor(sig);
237 if (log.isTraceEnabled()) {
238 log.trace(mon + " initialized");
239 }
240 }
241
242 /**
243 * Gets the MBean name of the registered {@link ProfileStatistic} bean.
244 *
245 * @return the MBean name
246 */
247 public ObjectName getMBeanName() {
248 return this.mbeanName;
249 }
250
251
252
253 ///// business logic (measurement, statistics and more) ///////////////
254
255 /**
256 * This method is called when the PerformanceMonitor is registered as
257 * shutdown hook.
258 *
259 * @see java.lang.Thread#run()
260 */
261 @Override
262 public void run() {
263 dumpStatistic();
264 }
265
266 /**
267 * Start.
268 *
269 * @param sig the sig
270 *
271 * @return the profile monitor
272 */
273 public static ProfileMonitor start(final Signature sig) {
274 return instance.startProfileMonitorFor(sig);
275 }
276
277 /**
278 * Start profile monitor for the given signature.
279 *
280 * @param sig the signature
281 * @return the profile monitor
282 */
283 public ProfileMonitor startProfileMonitorFor(final Signature sig) {
284 return this.startProfileMonitorFor(SignatureHelper.getAsString(sig));
285 }
286
287 /**
288 * Start profile monitor for the given signature.
289 *
290 * @param sig the signature
291 * @return the profile monitor
292 */
293 public ProfileMonitor startProfileMonitorFor(final String sig) {
294 ProfileMonitor mon = null;
295 if (jamonAvailable) {
296 mon = JamonMonitorFactory.getMonitor(sig, rootMonitor);
297 } else {
298 SimpleProfileMonitor parent = this.getSimpleProfileMonitor(sig);
299 mon = new SimpleProfileMonitor(sig, parent);
300 }
301 mon.start();
302 return mon;
303 }
304
305 private synchronized ProfileMonitor getMonitor(final Signature sig) {
306 if (jamonAvailable) {
307 return JamonMonitorFactory.getMonitor(sig, this.rootMonitor);
308 } else {
309 return getSimpleProfileMonitor(sig);
310 }
311 }
312
313 private SimpleProfileMonitor getSimpleProfileMonitor(final Signature sig) {
314 return this.getSimpleProfileMonitor(SignatureHelper.getAsString(sig));
315 }
316
317 private SimpleProfileMonitor getSimpleProfileMonitor(final String sig) {
318 SimpleProfileMonitor monitor = rootMonitor.getMonitor(sig);
319 if (monitor == null) {
320 monitor = new SimpleProfileMonitor(sig, rootMonitor);
321 }
322 return monitor;
323 }
324
325 private ProfileMonitor[] getMonitors() {
326 if (jamonAvailable) {
327 return JamonMonitorFactory.getMonitors(this.rootMonitor);
328 } else {
329 return rootMonitor.getMonitors();
330 }
331 }
332
333 /**
334 * Gets the sorted monitors.
335 *
336 * @return monitors sorted after total time (descending order)
337 */
338 protected final ProfileMonitor[] getSortedMonitors() {
339 ProfileMonitor[] monitors = getMonitors();
340 Arrays.sort(monitors);
341 return monitors;
342 }
343
344 private ProfileMonitor getMaxHitsMonitor() {
345 ProfileMonitor[] monitors = getMonitors();
346 ProfileMonitor max = new SimpleProfileMonitor();
347 for (int i = 0; i < monitors.length; i++) {
348 if (monitors[i].getHits() >= max.getHits()) {
349 max = monitors[i];
350 }
351 }
352 return max;
353 }
354
355 /**
356 * Gets the max hits.
357 *
358 * @return the max hits
359 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHits()
360 */
361 public int getMaxHits() {
362 return getMaxHitsMonitor().getHits();
363 }
364
365 /**
366 * Gets the max hits label.
367 *
368 * @return the max hits label
369 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHitsLabel()
370 */
371 public String getMaxHitsLabel() {
372 return getMaxHitsMonitor().getLabel();
373 }
374
375 /**
376 * Gets the max hits statistic.
377 *
378 * @return the max hits statistic
379 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxHitsStatistic()
380 */
381 public String getMaxHitsStatistic() {
382 return getMaxHitsMonitor().toShortString();
383 }
384
385 private ProfileMonitor getMaxTotalMonitor() {
386 ProfileMonitor[] monitors = getMonitors();
387 ProfileMonitor max = new SimpleProfileMonitor();
388 for (int i = 0; i < monitors.length; i++) {
389 if (monitors[i].getTotal() >= max.getTotal()) {
390 max = monitors[i];
391 }
392 }
393 return max;
394 }
395
396 /**
397 * Gets the max total.
398 *
399 * @return the max total
400 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotal()
401 */
402 public double getMaxTotal() {
403 return getMaxTotalMonitor().getTotal();
404 }
405
406 /**
407 * Gets the max total label.
408 *
409 * @return the max total label
410 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotalLabel()
411 */
412 public String getMaxTotalLabel() {
413 return getMaxTotalMonitor().getLabel();
414 }
415
416 /**
417 * Gets the max total statistic.
418 *
419 * @return the max total statistic
420 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxTotalStatistic()
421 */
422 public String getMaxTotalStatistic() {
423 return getMaxTotalMonitor().toShortString();
424 }
425
426 private ProfileMonitor getMaxAvgMonitor() {
427 ProfileMonitor[] monitors = getMonitors();
428 ProfileMonitor max = monitors[0];
429 double maxValue = 0.0;
430 for (int i = 0; i < monitors.length; i++) {
431 double value = monitors[i].getAvg();
432 if (!Double.isNaN(value) && (value > maxValue)) {
433 maxValue = value;
434 max = monitors[i];
435 }
436 }
437 return max;
438 }
439
440 /**
441 * Gets the root monitor.
442 *
443 * @return the root monitor
444 */
445 protected ProfileMonitor getRootMonitor() {
446 return this.rootMonitor;
447 }
448
449 /**
450 * Gets the max avg.
451 *
452 * @return the max avg
453 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvg()
454 */
455 public double getMaxAvg() {
456 return getMaxAvgMonitor().getAvg();
457 }
458
459 /**
460 * Gets the max avg label.
461 *
462 * @return the max avg label
463 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvgLabel()
464 */
465 public String getMaxAvgLabel() {
466 return getMaxAvgMonitor().getLabel();
467 }
468
469 /**
470 * Gets the max avg statistic.
471 *
472 * @return the max avg statistic
473 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxAvgStatistic()
474 */
475 public String getMaxAvgStatistic() {
476 return getMaxAvgMonitor().toShortString();
477 }
478
479 private ProfileMonitor getMaxMaxMonitor() {
480 ProfileMonitor[] monitors = getMonitors();
481 ProfileMonitor max = new SimpleProfileMonitor();
482 for (int i = 0; i < monitors.length; i++) {
483 if (monitors[i].getMax() >= max.getMax()) {
484 max = monitors[i];
485 }
486 }
487 return max;
488 }
489
490 /**
491 * Gets the max max.
492 *
493 * @return the max max
494 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMax()
495 */
496 public double getMaxMax() {
497 return getMaxMaxMonitor().getMax();
498 }
499
500 /**
501 * Gets the max max label.
502 *
503 * @return the max max label
504 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMaxLabel()
505 */
506 public String getMaxMaxLabel() {
507 return getMaxMaxMonitor().getLabel();
508 }
509
510 /**
511 * Gets the max max statistic.
512 *
513 * @return the max max statistic
514 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getMaxMaxStatistic()
515 */
516 public String getMaxMaxStatistic() {
517 return getMaxMaxMonitor().toShortString();
518 }
519
520 /**
521 * Gets the statistics.
522 *
523 * @return the statistics
524 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#getStatistics()
525 */
526 @SuppressWarnings("rawtypes")
527 public TabularData getStatistics() {
528 try {
529 String[] itemNames = { "Label", "Units", "Hits", "Avg", "Total",
530 "Min", "Max" };
531 String[] itemDescriptions = { "method name", "time unit (e.g. ms)",
532 "number of hits", "average time", "total time",
533 "minimal time", "maximal time" };
534 OpenType[] itemTypes = { SimpleType.STRING, SimpleType.STRING,
535 SimpleType.INTEGER, SimpleType.DOUBLE, SimpleType.DOUBLE,
536 SimpleType.DOUBLE, SimpleType.DOUBLE };
537 CompositeType rowType = new CompositeType("propertyType",
538 "property entry", itemNames, itemDescriptions, itemTypes);
539 TabularDataSupport data = MBeanHelper.createTabularDataSupport(rowType, itemNames);
540 ProfileMonitor[] monitors = getSortedMonitors();
541 if (monitors == null) {
542 log.warn("can't find monitors");
543 return null;
544 }
545 for (int i = 0; i < monitors.length; i++) {
546 Map<String, Object> map = new HashMap<String, Object>();
547 map.put("Label", monitors[i].getLabel());
548 map.put("Units", monitors[i].getUnits());
549 map.put("Hits", monitors[i].getHits());
550 map.put("Avg", monitors[i].getAvg());
551 map.put("Total", monitors[i].getTotal());
552 map.put("Min", monitors[i].getMin());
553 map.put("Max", monitors[i].getMax());
554 CompositeDataSupport compData = new CompositeDataSupport(
555 rowType, map);
556 data.put(compData);
557 }
558 return data;
559 } catch (OpenDataException e) {
560 log.error("can't create TabularData for log settings", e);
561 return null;
562 }
563 }
564
565 /**
566 * Log statistic.
567 *
568 * @see patterntesting.runtime.monitor.ProfileStatisticMBean#logStatistic()
569 */
570 public void logStatistic() {
571 log.info("----- Profile Statistic -----");
572 ProfileMonitor[] monitors = getSortedMonitors();
573 for (ProfileMonitor profMon : monitors) {
574 log.info("{}", profMon);
575 }
576 }
577
578 /**
579 * Dump statistic to a file in the temporary directory. Since 1.4.2 the
580 * filename is no longer "profile###.csv", but begins with the classname.
581 * The reason is located in the subclass SqlStatistic - now you can see
582 * which CSV file belongs to which statistic.
583 *
584 * @see ProfileStatisticMBean#dumpStatistic()
585 */
586 public void dumpStatistic() {
587 try {
588 File dumpFile = File.createTempFile(this.getClass().getSimpleName(), ".csv");
589 dumpStatisticTo(dumpFile);
590 } catch (IOException ioe) {
591 log.warn("Cannot dump statistic.", ioe);
592 }
593 }
594
595 /**
596 * Dump statistic to the given file
597 *
598 * @param dumpFile the dump file
599 * @throws IOException Signals that an I/O exception has occurred.
600 */
601 public void dumpStatisticTo(final File dumpFile) throws IOException {
602 ProfileMonitor[] monitors = getSortedMonitors();
603 if (monitors.length == 0) {
604 log.debug("no profiling data available");
605 return;
606 }
607 BufferedWriter writer = new BufferedWriter(new FileWriter(dumpFile));
608 writer.write(monitors[0].toCsvHeadline());
609 writer.newLine();
610 for (ProfileMonitor profMon : monitors) {
611 writer.write(profMon.toCsvString());
612 writer.newLine();
613 }
614 writer.close();
615 log.info("profiling data dumped to " + dumpFile);
616 }
617
618 /**
619 * Do you want to look for the monitor of a given method? Use this
620 * method here.
621 *
622 * @param clazz the clazz
623 * @param method the method name, including parameter
624 * e.g. "getProfileMonitor(Class,String)"
625 *
626 * @return monitor of the given class or null
627 */
628 @MayReturnNull
629 public ProfileMonitor getProfileMonitor(final Class<?> clazz, final String method) {
630 return this.getProfileMonitor(clazz.getName() + "." + method);
631 }
632
633 /**
634 * Do you want to look for the monitor of a given method? Use this method
635 * here.
636 *
637 * @param signature e.g. "hello.World(String[])"
638 *
639 * @return monitor for the given signature or null
640 * @since 1.4.2
641 */
642 @MayReturnNull
643 public ProfileMonitor getProfileMonitor(final Signature signature) {
644 return this.getProfileMonitor(SignatureHelper.getAsString(signature));
645 }
646
647 /**
648 * Do you want to look for the monitor of a given method? Use this
649 * method here.
650 *
651 * @param signature e.g. "hello.World(String[])"
652 *
653 * @return monitor for the given signature or null
654 */
655 @MayReturnNull
656 public ProfileMonitor getProfileMonitor(final String signature) {
657 for (ProfileMonitor profMon : this.getMonitors()) {
658 if (signature.equals(profMon.getLabel())) {
659 return profMon;
660 }
661 }
662 log.trace("no ProfileMonitor for " + signature + " found");
663 return null;
664 }
665
666 /**
667 * To string.
668 *
669 * @return the string
670 * @see java.lang.Thread#toString()
671 */
672 @Override
673 public String toString() {
674 return this.mbeanName.toString();
675 }
676
677 }