001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.util;
017
018import java.text.DecimalFormat;
019import java.util.Date;
020import java.util.Deque;
021import java.util.Map;
022import java.util.Map.Entry;
023import java.util.concurrent.Callable;
024
025import org.apache.log4j.Logger;
026
027/**
028 * Performance monitoring log utility.
029 * 
030 * @author Kuali Rice Team (rice.collab@kuali.org)
031 * @version 2.4
032 */
033public class ProcessLogger {
034
035    private static final Logger LOG = Logger.getLogger(ProcessLogger.class);
036
037    /**
038     * Thread local mapping of process status trackers.
039     */
040    private static final ThreadLocal<Map<String, ProcessStatus>> TL_STAT = new ThreadLocal<Map<String, ProcessStatus>>();
041
042    /**
043     * Tracks statistics and execution count for common operations.
044     * 
045     * <p>
046     * ProcessCounter is typically used to clock calls to external systems, such as database and web
047     * service requests.
048     * </p>
049     */
050    private static class ProcessCounter {
051
052        /**
053         * The name of the counter.
054         */
055        private final String name;
056
057        /**
058         * Stack of the start times for currently active executions.
059         */
060        private Deque<Long> start = new java.util.LinkedList<Long>();
061
062        /**
063         * The total count of all executions.
064         */
065        private long count;
066
067        /**
068         * The minimum execution time, calculated among all executions within the same process.
069         */
070        private long min;
071
072        /**
073         * The maximum execution time, calculated among all executions within the same process.
074         */
075        private long max;
076
077        /**
078         * The average (mean) execution time, calculated among all executions within the same
079         * process.
080         */
081        private long avg;
082
083        /**
084         * A detailed description of the execution responsible for the max execution time.
085         */
086        private String longest;
087
088        /**
089         * Create a new counter for use within the current process.
090         * 
091         * @param name The name of the counter.
092         */
093        private ProcessCounter(String name) {
094            this.name = name;
095        }
096    }
097
098    /**
099     * Tracks the status of one more processes actively being monitoring on the current Thread.
100     * 
101     * <p>
102     * This class represents the internal state of most of ProcessLogger's operations.
103     * </p>
104     */
105    private static class ProcessStatus {
106
107        /**
108         * The name of this process trace.
109         */
110        private final String name;
111
112        /**
113         * The start time the process started.
114         */
115        private final long startTime;
116
117        /**
118         * Heap free space, recorded at the start of the process.
119         */
120        private final long startFree;
121
122        /**
123         * Heap total available space, recorded at the start of the process.
124         */
125        private final long startTot;
126
127        /**
128         * Heap max available space, recorded at the start of the process.
129         */
130        private final long startMax;
131
132        /**
133         * The time of the last trace message reported on this process.
134         */
135        private long lastTime;
136
137        /**
138         * Heap free space, recorded at the time the last trace message was reported on this
139         * process.
140         */
141        private long lastFree;
142
143        /**
144         * Heap total available space, recorded at the time the last trace message was reported on
145         * this process.
146         */
147        private long lastTot;
148
149        /**
150         * Heap max available space, recorded at the time the last trace message was reported on
151         * this process.
152         */
153        private long lastMax;
154
155        /**
156         * The time, in milliseconds, elapsed between the last trace message reported and the
157         * previous report.
158         */
159        private long diffTime;
160
161        /**
162         * The difference in heap free space between the last trace message reported and the
163         * previous report.
164         */
165        private long diffFree;
166
167        /**
168         * The difference in heap total space between the last trace message reported and the
169         * previous report.
170         */
171        private long diffTot;
172
173        /**
174         * Internal mapping of counters tracked on this process.
175         */
176        private Map<String, ProcessCounter> counters = new java.util.LinkedHashMap<String, ProcessCounter>();
177
178        /**
179         * Internal mapping of ntrace counters.
180         * 
181         * <p>
182         * These counters are used for detecting excessive execution counts of operations that are
183         * typically fast, but may become expensive with a high number of executions. For example,
184         * ntrace counts may be used to track excessive object creation.
185         * </p>
186         */
187        private Map<String, Long> ntraceCount = new java.util.TreeMap<String, Long>();
188
189        /**
190         * Internal mapping of ntrace thresholds.
191         * 
192         * <p>
193         * Associated with ntraceCount, this map defines thresholds below which not to report counts.
194         * </p>
195         */
196        private Map<String, Long> ntraceThreshold = new java.util.HashMap<String, Long>();
197
198        /**
199         * Verbose operation flag.
200         * 
201         * <p>
202         * When true, all trace messages will be included regardless of elapsed time. When false,
203         * trace messages will only be included when elapsed time is 1 or more milliseconds.
204         * </p>
205         * 
206         * <p>
207         * By default, verbose is true when debugging is enabled via {@link #LOG}.
208         * </p>
209         */
210        private boolean verbose = LOG.isDebugEnabled();
211
212        /**
213         * StringBuilder for constructing the trace message for reporting via log4j.
214         */
215        private StringBuilder traceBuffer = new StringBuilder();
216
217        /**
218         * StringBuilder for collecting extra information to included at the end of the trace.
219         */
220        private StringBuilder extra = new StringBuilder();
221
222        /**
223         * Create a status tracker for a new process.
224         * 
225         * @param name The name of the process.
226         */
227        private ProcessStatus(String name) {
228            this.name = name;
229            this.startTime = System.currentTimeMillis();
230            this.startFree = Runtime.getRuntime().freeMemory();
231            this.startTot = Runtime.getRuntime().totalMemory();
232            this.startMax = Runtime.getRuntime().maxMemory();
233            this.lastTime = startTime;
234            this.lastFree = startFree;
235            this.lastTot = startTot;
236            this.lastMax = startMax;
237        }
238
239        /**
240         * Report that time has elapsed on the process.
241         * <p>
242         * This method updates the following fields based on the current system runtime.
243         * <ul>
244         * <li>{@link #diffTime}</li>
245         * <li>{@link #diffFree}</li>
246         * <li>{@link #diffTot}</li>
247         * <li>{@link #lastTime}</li>
248         * <li>{@link #lastFree}</li>
249         * <li>{@link #lastTot}</li>
250         * <li>{@link #lastMax}</li>
251         * </ul>
252         */
253        private void elapse() {
254            long nTime = System.currentTimeMillis();
255            long nFree = Runtime.getRuntime().freeMemory();
256            long nTot = Runtime.getRuntime().totalMemory();
257            long nMax = Runtime.getRuntime().maxMemory();
258            diffTime = nTime - lastTime;
259            diffFree = nFree - lastFree;
260            diffTot = nTot - lastTot;
261            lastTime = nTime;
262            lastFree = nFree;
263            lastTot = nTot;
264            lastMax = nMax;
265        }
266
267        /**
268         * Mark the start of a new countable operation in the current process.
269         * 
270         * @param name The name of the process counter.
271         */
272        private void countBegin(String name) {
273            ProcessCounter pc = counters.get(name);
274            if (pc == null) {
275                pc = new ProcessCounter(name);
276                counters.put(name, pc);
277            }
278            pc.start.push(new Long(System.currentTimeMillis()));
279        }
280
281        /**
282         * Mark the end of a countable operation previously reported via {@link #countBegin(String)}
283         * .
284         * 
285         * @param name The name of the process counter.
286         * @param detail Details on the operation that just ended.
287         * @return The process counter.
288         */
289        private ProcessCounter countEnd(String name, String detail) {
290            ProcessCounter pc = counters.get(name);
291            if (pc == null || pc.start.isEmpty()) {
292                return null;
293            }
294
295            long start = pc.start.pop();
296            long elapsed = System.currentTimeMillis() - start;
297            
298            if (elapsed < pc.min || pc.count == 0L) {
299                pc.min = elapsed;
300            }
301
302            if (elapsed > pc.max) {
303                pc.max = elapsed;
304                pc.longest = detail;
305            }
306
307            pc.count++;
308            pc.avg = (pc.avg * (pc.count - 1) + elapsed) / pc.count;
309
310            return pc;
311        }
312
313        /**
314         * Write a trace header on the process trace.
315         * 
316         * @param name The name of the process trace.
317         * @param processDescription A description of the process trace.
318         */
319        private void appendTraceHeader(String name, String processDescription) {
320            // Write a description of the process to the trace buffer.
321            traceBuffer.append("KRAD Process Trace (");
322            traceBuffer.append(name);
323            traceBuffer.append("): ");
324            traceBuffer.append(processDescription);
325
326            // Write stack information related to method controlling the callable process.
327            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
328            String callerClassName = stackTrace[3].getClassName();
329            int indexOfPeriod = callerClassName.lastIndexOf('.');
330            String callerPackageName = "";
331            if (indexOfPeriod != -1) {
332                callerPackageName = callerClassName.substring(0, indexOfPeriod);
333            }
334            int stackTraceIndex = 3;
335            while (stackTraceIndex < stackTrace.length - 1
336                    && (stackTrace[stackTraceIndex].getClassName().startsWith("sun.")
337                            || stackTrace[stackTraceIndex].getClassName().startsWith("java.")
338                            || stackTrace[stackTraceIndex].getClassName().startsWith(
339                                    ProcessLogger.class.getPackage().getName()) || stackTrace[stackTraceIndex]
340                            .getClassName().startsWith(callerPackageName))) {
341                if (!ProcessLogger.class.getName().equals(stackTrace[stackTraceIndex].getClassName())) {
342                    traceBuffer.append("\n  at ").append(stackTrace[stackTraceIndex]);
343                }
344                stackTraceIndex++;
345            }
346            traceBuffer.append("\n  at ").append(stackTrace[stackTraceIndex]);
347
348            // Write initial heap state and start time.
349            traceBuffer.append("\nInitial Memory Usage: ");
350            traceBuffer.append(memoryToString(startFree, startTot,
351                    startMax));
352
353            if (LOG.isInfoEnabled() && verbose) {
354                LOG.debug("Processing Started\n" + traceBuffer.toString());
355            }
356        }
357
358        /**
359         * Write a log message on this process trace.
360         * 
361         * @param message The message to write.
362         */
363        public void appendTraceMessage(String message) {
364            if (LOG.isDebugEnabled() && verbose) {
365                LOG.debug(message
366                        + " ("
367                        + name
368                        + ")\nElapsed Time: "
369                        + intervalToString(diffTime)
370                        + "\nMemory Usage: "
371                        + memoryToString(lastFree, lastTot,
372                                lastMax)
373                        + "\nMemory Delta: "
374                        + memoryToString(diffFree, lastTot,
375                                lastMax) + " - tot delta: "
376                        + sizeToString(diffTot));
377            }
378
379            if (LOG.isInfoEnabled() && (verbose || diffTime > 0L)) {
380                traceBuffer.append('\n');
381
382                if (message.length() < 40) {
383                    traceBuffer.append(message);
384                    for (int i = message.length(); i < 40; i++) {
385                        traceBuffer.append('.');
386                    }
387                } else {
388                    traceBuffer.append(message.substring(0, 40));
389                }
390
391                traceBuffer.append(intervalToString(diffTime));
392                traceBuffer.append(' ');
393                traceBuffer.append(intervalToString(lastTime
394                        - startTime));
395                traceBuffer.append(' ');
396                traceBuffer.append(sizeToString(lastFree));
397                traceBuffer.append(' ');
398                traceBuffer.append(sizeToString(diffFree));
399            }
400        }
401
402        /**
403         * Write a trace footer on the process trace.
404         */
405        public void appendTraceFooter() {
406            traceBuffer.append('\n');
407            String message = "Processing Complete";
408            traceBuffer.append(message);
409            for (int i = message.length(); i < 40; i++) {
410                traceBuffer.append('.');
411            }
412            traceBuffer.append(intervalToString(diffTime));
413            traceBuffer.append(' ');
414            traceBuffer.append(intervalToString(lastTime
415                    - startTime));
416            traceBuffer.append(' ');
417            traceBuffer.append(sizeToString(lastFree));
418            traceBuffer.append(' ');
419            traceBuffer.append(sizeToString(diffFree));
420            if (!ntraceCount.isEmpty()) {
421                traceBuffer.append("\nMonitors:");
422                for (Entry<String, Long> ce : ntraceCount.entrySet()) {
423                    
424                    Long threshold = ntraceThreshold.get(ce.getKey());
425                    if (threshold != null && threshold >= ce.getValue()) {
426                        continue;
427                    }
428                    
429                    traceBuffer.append("\n  ");
430                    StringBuilder sb = new StringBuilder(ce.getKey());
431                    int iocc = sb.indexOf("::");
432                    if (iocc == -1)
433                        sb.append(":" + ce.getValue());
434                    else
435                        sb.insert(iocc + 1, ce.getValue());
436                    traceBuffer.append(sb);
437                }
438            }
439            if (!counters.isEmpty()) {
440                traceBuffer.append("\nCounters:");
441                for (ProcessCounter pc : counters.values()) {
442                    traceBuffer.append("\n  ");
443                    traceBuffer.append(pc.name);
444                    traceBuffer.append(": ");
445                    traceBuffer.append(pc.count);
446                    traceBuffer.append(" (");
447                    traceBuffer.append(intervalToString(pc.min));
448                    traceBuffer.append("/");
449                    traceBuffer.append(intervalToString(pc.max));
450                    traceBuffer.append("/");
451                    traceBuffer.append(intervalToString(pc.avg));
452                    traceBuffer.append(")");
453                    if (pc.longest != null && !"".equals(pc.longest)) {
454                        traceBuffer.append("\n    longest : ");
455                        traceBuffer.append(pc.longest);
456                    }
457                }
458            }
459            traceBuffer.append("\nElapsed Time: ");
460            traceBuffer.append(intervalToString(lastTime
461                    - startTime));
462            traceBuffer.append("\nMemory Usage: ");
463            traceBuffer.append(memoryToString(lastFree, lastTot,
464                    lastMax));
465            traceBuffer.append("\nMemory Delta: ");
466            traceBuffer.append(memoryToString(lastFree
467                    - startFree, lastTot, lastMax));
468            traceBuffer.append(" - tot delta: ");
469            traceBuffer.append(sizeToString(lastTot - startTot));
470        }
471
472    }
473
474    /**
475     * Print a human readable time duration.
476     * 
477     * @param millis The number of milliseconds.
478     * @return A human readable representation of the time interval represented by millis.
479     */
480    public static String intervalToString(long millis) {
481        DecimalFormat df = new DecimalFormat("000");
482        StringBuilder sb = new StringBuilder();
483        sb.append('.');
484        sb.append(df.format(millis % 1000));
485        df.applyPattern("00");
486        long sec = millis / 1000;
487        sb.insert(0, df.format(sec % 60));
488        long min = sec / 60;
489        sb.insert(0, ':');
490        sb.insert(0, df.format(min % 60));
491        long hours = min / 60;
492        if (hours > 0) {
493            sb.insert(0, ':');
494            sb.insert(0, df.format(hours % 24));
495        }
496        long days = hours / 24;
497        if (days > 0) {
498            sb.insert(0, " days, ");
499            sb.insert(0, days);
500        }
501
502        return sb.toString();
503    }
504
505    /**
506     * The steps for printing sizes.
507     */
508    private static final String[] SIZE_INTERVALS = new String[]{"k", "M",
509            "G", "T", "E",};
510
511    /**
512     * Print a human readable size.
513     * 
514     * @param bytes The number of bytes.
515     * @return A human readable representation of the size.
516     */
517    public static String sizeToString(long bytes) {
518        DecimalFormat df = new DecimalFormat("000");
519        StringBuilder sb = new StringBuilder();
520        int i = -1;
521        int mod = 0;
522
523        if (bytes < 0) {
524            sb.append('-');
525            bytes = Math.abs(bytes);
526        }
527
528        while (bytes / 1024 > 0 && i < SIZE_INTERVALS.length) {
529            i++;
530            mod = (int) (bytes % 1024);
531            bytes /= 1024;
532        }
533        sb.append(bytes);
534
535        if (mod > 0) {
536            sb.append('.');
537            sb.append(df.format(mod * 1000 / 1024));
538        }
539
540        if (i >= 0) {
541            sb.append(SIZE_INTERVALS[i]);
542        }
543
544        return sb.toString();
545    }
546
547    /**
548     * Get a human readable representation of the system memory.
549     * 
550     * @param free free heap memory, in bytes
551     * @param tot total heap memory, in bytes
552     * @param max maximum total heap memory, in bytes
553     * 
554     * @return A human readable representation of the system memory.
555     */
556    public static String memoryToString(long free, long tot, long max) {
557        StringBuilder sb = new StringBuilder();
558        sb.append(sizeToString(free));
559        sb.append('/');
560        sb.append(sizeToString(tot));
561        sb.append('/');
562        sb.append(sizeToString(max));
563        sb.append(" - ");
564        sb.append(free * 100 / tot);
565        sb.append("% free");
566        return sb.toString();
567    }
568
569    /**
570     * Follow a callable process using the system default verbose setting and checked exception
571     * handling.
572     * 
573     * @param <T> callable return type
574     * @param name The name of the process.
575     * @param processDescription A message describing the process to report at the top of the trace.
576     * @param callableProcess The callable process.
577     * @return The result of calling the process.
578     * @throws IllegalStateException If checked exception occurs within the callable process.
579     */
580    public static <T> T safeFollow(final String name, final String processDescription,
581            final Callable<T> callableProcess) {
582        try {
583            return follow(name, processDescription, null, callableProcess);
584        } catch (Exception e) {
585            if (e instanceof RuntimeException) {
586                throw (RuntimeException) e;
587            } else {
588                throw new IllegalStateException("Error in followed process " + name + " - " + processDescription, e);
589            }
590        }
591    }
592
593    /**
594     * Follow a callable process with checked exception handling.
595     * 
596     * @param <T> callable return type
597     * @param name The name of the process.
598     * @param processDescription A message describing the process to report at the top of the trace.
599     * @param verbose Verbose operation flag, see {@link ProcessStatus#verbose}.
600     * @param callableProcess The callable process.
601     * @return The result of calling the process.
602     * @throws IllegalStateException If checked exception occurs within the callable process.
603     */
604    public static <T> T safeFollow(final String name, final String processDescription,
605            final Boolean verbose, final Callable<T> callableProcess) {
606        try {
607            return follow(name, processDescription, verbose, callableProcess);
608        } catch (Exception e) {
609            if (e instanceof RuntimeException) {
610                throw (RuntimeException) e;
611            } else {
612                throw new IllegalStateException("Error in followed process " + name + " - " + processDescription, e);
613            }
614        }
615    }
616
617    /**
618     * Follow a callable process using the system default verbose setting.
619     * 
620     * @param <T> callable return type
621     * @param name The name of the process.
622     * @param processDescription A message describing the process to report at the top of the trace.
623     * @param callableProcess The callable process.
624     * @return The result of calling the process.
625     * @throws Exception from {@link Callable#call()}
626     */
627    public static <T> T follow(String name, String processDescription, Callable<T> callableProcess)
628            throws Exception {
629        return follow(name, processDescription, null, callableProcess);
630    }
631
632    /**
633     * Follow a callable process.
634     * 
635     * @param <T> callable return type
636     * @param name The name of the process.
637     * @param processDescription A message describing the process to report at the top of the trace.
638     * @param verbose True to note every trace entry, false to only note those that take longer than 1ms.
639     * @param callableProcess The callable process.
640     * @return The result of calling the process.
641     * @throws Exception from {@link Callable#call()}
642     */
643    public static <T> T follow(String name, String processDescription, Boolean verbose,
644            Callable<T> callableProcess) throws Exception {
645        // When logging is not at least info enabled, process tracing has no effect - short circuit.
646        if (!LOG.isInfoEnabled()) {
647            return callableProcess.call();
648        }
649
650        // Ensure that statistics are not already active for a process with the same name.
651        assert TL_STAT.get() == null || TL_STAT.get().get(name) == null;
652
653        // Bind a new status tracking map to the current thread, if not already bound.
654        if (TL_STAT.get() == null) {
655            TL_STAT.set(new java.util.HashMap<String, ProcessStatus>());
656        }
657
658        // Create a new status tracker for monitoring this process.
659        ProcessStatus processStatus = new ProcessStatus(name);
660        if (verbose != null) {
661            processStatus.verbose = verbose;
662        }
663
664        try {
665            // Bind the status tracker to the current thread.
666            TL_STAT.get().put(name, processStatus);
667
668            processStatus.appendTraceHeader(name, processDescription);
669
670            // Call the process.
671            return callableProcess.call();
672
673        } finally {
674            // Clear the thread state to prevent memory growth.
675            if (TL_STAT.get() != null) {
676                TL_STAT.get().remove(name);
677                if (TL_STAT.get().isEmpty())
678                    TL_STAT.remove();
679            }
680
681            // Calculate time and heap utilization since the last trace message.
682            processStatus.elapse();
683            processStatus.appendTraceFooter();
684
685            // Write process trace at INFO level
686            LOG.info(processStatus.traceBuffer.toString());
687        }
688    }
689
690    /**
691     * Determine if any process traces are active on the current thread.
692     * 
693     * @return True if any process traces are active on the current thread, false if not.
694     */
695    public static boolean isTraceActive() {
696        return TL_STAT.get() != null && !TL_STAT.get().isEmpty();
697    }
698
699    /**
700     * Determine if a process trace is active on the current thread.
701     * 
702     * @param name The name of the process trace.
703     * @return True if the named process trace is active on the current thread, false if not.
704     */
705    public static boolean isTraceActive(String name) {
706        return TL_STAT.get() != null && TL_STAT.get().containsKey(name);
707    }
708
709    /**
710     * Determine if the named process is active on the current thread with the verbose flag set to
711     * true.
712     * 
713     * @param name The name of the process trace.
714     * @return True if the named process trace is active on the current thread with the verbose flag
715     *         set to true, false if not.
716     */
717    public static boolean isVerbose(String name) {
718        ProcessStatus processStatus = TL_STAT.get() == null ? null : TL_STAT.get().get(
719                name);
720        return processStatus != null && processStatus.verbose;
721    }
722
723    /**
724     * Modify the verbose flag on a process trace active on the current thread.
725     * 
726     * <p>
727     * This method has no impact if a process trace with the given name is not active.
728     * </p>
729     * 
730     * @param name The name of the process trace.
731     * @param verbose The verbose flag setting to apply to the named process trace.
732     */
733    public static void setVerbose(String name, boolean verbose) {
734        ProcessStatus ps = TL_STAT.get() == null ? null : TL_STAT.get().get(
735                name);
736        if (ps != null) {
737            ps.verbose = verbose;
738        }
739    }
740
741    /**
742     * Report a trace message on all process traces active on the current thread.
743     * 
744     * <p>
745     * The first 40 characters of the message will be printed on the traces along with timing and
746     * heap utilization statistics.
747     * </p>
748     * 
749     * <p>
750     * When debug logging is enabled, the entire message will be printed via log4j at the DEBUG
751     * level.
752     * </p>
753     * 
754     * @param message The message to report on the trace.
755     */
756    public static void trace(String message) {
757        if (TL_STAT.get() != null) {
758            for (String k : TL_STAT.get().keySet()) {
759                trace(k, message);
760            }
761        }
762    }
763
764    /**
765     * Report a trace message on a process trace, if active on the current thread.
766     * 
767     * <p>
768     * The first 40 characters of the message will be printed on the trace along with timing and
769     * heap utilization statistics.
770     * </p>
771     * 
772     * <p>
773     * When debug logging is enabled, the entire message will be printed via log4j at the DEBUG
774     * level.
775     * </p>
776     * 
777     * @param name The name of the process trace.
778     * @param message The message to report on the trace.
779     */
780    public static void trace(String name, String message) {
781        ProcessStatus processStatus = TL_STAT.get() == null ? null : TL_STAT.get().get(
782                name);
783        if (processStatus != null) {
784            processStatus.elapse();
785            processStatus.appendTraceMessage(message);
786        }
787    }
788
789    /**
790     * Count instances of a typically fast operation that may become expensive given a high number
791     * of executions.
792     * 
793     * <p>
794     * When the specified number of instances of the same operation have been counted, then a
795     * message indicating the execution count will be added to the process trace.
796     * </p>
797     * 
798     * @param prefix The message to report before the count.
799     * @param suffix The message to report after the count.
800     * @param interval The number of instances to count between reports on the process trace.
801     * @return The execution count of the operation on trace with the highest number of executions.
802     */
803    public static long ntrace(String prefix, String suffix, long interval) {
804        return ntrace(prefix, suffix, interval, 0L);
805    }
806    
807    /**
808     * Count instances of a typically fast operation that may become expensive given a high number
809     * of executions.
810     * 
811     * <p>
812     * When the specified number of instances of the same operation have been counted, then a
813     * message indicating the execution count will be added to the process trace.
814     * </p>
815     * 
816     * @param prefix The message to report before the count.
817     * @param suffix The message to report after the count.
818     * @param interval The number of instances to count between reports on the process trace.
819     * @param threshold The number of instances below which not to report monitored counts.
820     * @return The execution count of the operation on trace with the highest number of executions.
821     */
822    public static long ntrace(String prefix, String suffix, long interval, long threshold) {
823        long rv = 0L;
824        if (TL_STAT.get() != null) {
825            for (String k : TL_STAT.get().keySet()) {
826                rv = Math.max(rv, ntrace(k, prefix, suffix, interval, threshold));
827            }
828        }
829
830        return rv;
831    }
832
833    /**
834     * Count instances of a typically fast operation that may become expensive given a high number
835     * of executions.
836     * 
837     * <p>
838     * When the specified number of instances of the same operation have been counted, then a
839     * message indicating the execution count will be added to the process trace.
840     * </p>
841     * 
842     * @param name The name of the trace.
843     * @param prefix The message to report before the count.
844     * @param suffix The message to report after the count.
845     * @param interval The number of instances to count between reports on the process trace.
846     * @return The execution count of the operation on the named trace.
847     */
848    public static long ntrace(String name, String prefix, String suffix,
849            long interval) {
850        return ntrace(name, prefix, suffix, interval, 0L);
851    }
852
853    /**
854     * Count instances of a typically fast operation that may become expensive given a high number
855     * of executions.
856     * 
857     * <p>
858     * When the specified number of instances of the same operation have been counted, then a
859     * message indicating the execution count will be added to the process trace.
860     * </p>
861     * 
862     * @param name The name of the trace.
863     * @param prefix The message to report before the count.
864     * @param suffix The message to report after the count.
865     * @param interval The number of instances to count between reports on the process trace.
866     * @param threshold The number of instances below which not to report monitored counts.
867     * @return The execution count of the operation on the named trace.
868     */
869    public static long ntrace(String name, String prefix, String suffix,
870            long interval, long threshold) {
871        ProcessStatus processStatus = TL_STAT.get() == null ? null : TL_STAT.get().get(
872                name);
873        String nTraceCountKey = prefix + suffix;
874        Long nTraceCount = processStatus.ntraceCount.get(nTraceCountKey);
875        
876        if (nTraceCount == null) {
877            nTraceCount = 0L;
878        }
879        
880        processStatus.ntraceCount.put(nTraceCountKey, ++nTraceCount);
881        
882        if (threshold > 0) {
883            processStatus.ntraceThreshold.put(nTraceCountKey, threshold);
884        }
885        
886        if (nTraceCount % interval == 0) {
887            String msg = prefix + nTraceCount + suffix;
888            trace(msg);
889            if (LOG.isDebugEnabled()) {
890                LOG.debug(msg, new Throwable());
891            }
892        }
893
894        return nTraceCount;
895    }
896
897    /**
898     * Mark the start of a new countable operation on all active process traces.
899     * 
900     * @param name The name of the process counter.
901     */
902    public static void countBegin(String name) {
903        if (TL_STAT.get() != null) {
904            for (String k : TL_STAT.get().keySet()) {
905                countBegin(k, name);
906            }
907        }
908    }
909
910    /**
911     * Mark the start of a new countable operation on an active process trace.
912     * 
913     * @param traceName The name of the process trace.
914     * @param name The name of the process counter.
915     */
916    public static void countBegin(String traceName, String name) {
917        ProcessStatus ps = TL_STAT.get() == null ? null : TL_STAT.get().get(
918                traceName);
919        if (ps != null) {
920            ps.countBegin(name);
921        }
922    }
923
924    /**
925     * Mark the end of a countable operation previously reported via {@link #countBegin(String)} .
926     * 
927     * @param name The name of the process counter.
928     * @param detail Details on the operation that just ended.
929     */
930    public static void countEnd(String name, String detail) {
931        if (TL_STAT.get() != null) {
932            for (String k : TL_STAT.get().keySet()) {
933                countEnd(k, name, detail);
934            }
935        }
936    }
937
938    /**
939     * Mark the end of a countable operation previously reported via
940     * {@link #countBegin(String, String)} .
941     * 
942     * @param traceName The name of the process trace.
943     * @param name The name of the process counter.
944     * @param detail Details on the operation that just ended.
945     */
946    public static void countEnd(String traceName, String name,
947            String detail) {
948        ProcessStatus processStatus = TL_STAT.get() == null ? null : TL_STAT.get().get(
949                traceName);
950        if (processStatus != null) {
951            processStatus.countEnd(name, detail);
952        }
953    }
954
955    /**
956     * Append an informational message to a process trace.
957     * 
958     * <p>
959     * The message will additionally be logged at the INFO level.
960     * </p>
961     * 
962     * @param traceName The name of the process trace.
963     * @param message The information message.
964     */
965    public static void addExtra(String traceName, Object message) {
966        ProcessStatus processStatus = TL_STAT.get() == null ? null : TL_STAT.get().get(
967                traceName);
968        if (processStatus == null) {
969            return;
970        }
971        StringBuilder sb = new StringBuilder();
972        sb.append("Information Message Reported at ");
973        sb.append(new Date());
974        sb.append(":\n");
975        sb.append(message);
976        LOG.info(sb.toString());
977        processStatus.extra.append(sb);
978        processStatus.extra.append("\n\n");
979    }
980
981}