All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.util.logging.StatusReport Maven / Gradle / Ivy

//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later
// version.
//
// This library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
//
// Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330,
// Boston, MA
// 02111-1307 USA
//
// The Initial Developer of the Original Code is Charles W. Rapp.
// Portions created by Charles W. Rapp are
// Copyright (C) 2001 - 2005, 2021. Charles W. Rapp.
// All Rights Reserved.
//

package net.sf.eBus.util.logging;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.util.TimerEvent;
import net.sf.eBus.util.TimerTask;
import net.sf.eBus.util.TimerTaskListener;

/**
 * Writes a status report to a log at a specified
 * interval. The default interval is every 15 minutes on the
 * quarter hour. The default
 * {@code java.util.logging.Logger} is the application's
 * root log. The default logging level is
 * {@code java.util.logging.Level.INFO}.
 * 

* When the timer expires it generates a report containing: *

    *
  • * The application name, version and build date. *
  • *
  • * The JVM's statistics: version and memory usage. *
  • *
  • * Each registered * {@link net.sf.eBus.util.logging.StatusReporter}'s * statistics. *
  • *
* @see net.sf.eBus.util.logging.StatusReporter * * @author Charles Rapp */ public final class StatusReport implements TimerTaskListener { //--------------------------------------------------------------- // Enums. // /** * Enumerates the allowed status report frequencies: *
    *
  1. * FIVE_MINUTE: generate a report every five minutes on * the five minute. *
  2. *
  3. * TEN_MINUTE: generate a report every ten minutes on the * ten minute; *
  4. *
  5. * FIFTEEN_MINUTE: generate a report every fifteen * minutes on the quarter hour. *
  6. *
  7. * TWENTY_MINUTE: generate a report every twenty minutes * on the twenty minute. *
  8. *
  9. * THIRTY_MINUTE: generate a report every thirty minutes * on the half hour. *
  10. *
  11. * HOURLY: generate a report every hour on the hour. *
  12. *
*/ public enum ReportFrequency { /** * Report every one minute. Used for testing purposes. */ THIRTY_SECONDS (30_000), /** * Report every five minutes. */ FIVE_MINUTE (300_000), /** * Report every ten minutes. */ TEN_MINUTE (600_000), /** * Report every fifteen minutes. */ FIFTEEN_MINUTE (900_000), /** * Report every twenty minutes. */ TWENTY_MINUTE (1_200_000), /** * Report every thirty minutes. */ THIRTY_MINUTE (1_800_000), /** * Report every sixty minutes. */ HOURLY (3_600_000); //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // // The reporting frequency in milliseconds. private final long mFrequency; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // // Creates a report frequecy instance. private ReportFrequency(final long frequency) { mFrequency = frequency; } // end of ReportFrequency(long) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns the report frequency in milliseconds. * @return the report frequency in milliseconds. */ public long getFrequency() { return (mFrequency); } // end of getFrequency() // // end of Get Methods. //------------------------------------------------------- } // end of enum ReportFrequency //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants // private static final String THREAD_HEADER = "| Thread Name | ID | State |"; private static final String THREAD_HEADER_2 = "+----------------------+------------+-----------------+"; //----------------------------------------------------------- // Statics. // // Make one and only one StatusReport instance. private static StatusReport sInstance; private static final Lock sCtorMutex = new ReentrantLock(); // Remember when this application was started. private static final Date sStartTime = new Date(); private static final Timer sTimer = new Timer("ReportTimer", true); //----------------------------------------------------------- // Locals. // // Set this flag to true when the application information // has been set. The application information may only // be set once. private boolean mInitFlag; // The application's name, version, build date and copyright // boilerplate. private String mAppName; private String mVersion; private Date mBuildDate; private String mBoilerPlate; // Write the status report to this log. private Logger mLogger; // Write the status report to this log level. private Level mLevel; // Generate the status report at this frequency. private ReportFrequency mFrequency; // The registered status reporters list. private final List mReporters; // When this timer expires, generate the status report. private TimerTask mReportTimer; // If this flag is true, then print out the status of all // active threads. This flag is false by default. private boolean mDumpThreadsFlag; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // // Create the status report object and start the timer. private StatusReport() { mInitFlag = false; mAppName = ""; mVersion = ""; mBuildDate = null; mBoilerPlate = null; mLevel = Level.INFO; mFrequency = ReportFrequency.FIFTEEN_MINUTE; // Do *not* dump the active threads by default. mDumpThreadsFlag = false; // Use the root logger by default mLogger = Logger.getLogger(""); mReporters = new LinkedList<>(); } // end of StatusReport() // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // TimerTaskListener Interface Implementation // /** * Time to output another status report. *

* DO NOT CALL THIS METHOD! * This method is called when the report timer expires. * @param event the expired timer event. */ @Override public void handleTimeout(final TimerEvent event) { final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); final Runtime sys = Runtime.getRuntime(); final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); final long now = System.currentTimeMillis(); final long upTime = now - sStartTime.getTime(); final long totalMem = sys.totalMemory(); final long freeMem = sys.freeMemory(); final long usedMem = totalMem - freeMem; final List reporters; final String report; pw.println("STATUS REPORT"); pw.println(); // Report system wide data first. logHeader(pw); pw.println(); pw.print(" JRE: "); pw.print(System.getProperty("java.version")); pw.print(" ("); pw.print(System.getProperty("java.vendor")); pw.println(")"); pw.print(" JVM: "); pw.print(System.getProperty("java.vm.name")); pw.print(", "); pw.print(System.getProperty("java.vm.version")); pw.print(" ("); pw.print(System.getProperty("java.vm.vendor")); pw.println(")"); pw.println(); pw.print(" Start date: "); pw.format("%1$tm/%1$td/%1$tY at %1$tH:%1$tM:S%n", sStartTime); pw.print(" Up time: "); pw.println(formatTime(upTime)); pw.format(" Used memory: %,d bytes%n", usedMem); pw.format(" Free memory: %,d bytes%n", freeMem); pw.format(" Total memory: %,d bytes%n", totalMem); pw.format(" Thread count: %,d (peak %,d)%n%n", threadBean.getThreadCount(), threadBean.getPeakThreadCount()); // Print out the active threads if so requested. if (mDumpThreadsFlag) { outputThreads(pw, threadBean); pw.println(); } // Copy the reporters list and iterate over that. // This allows reports to deregister while the status // report is generated. synchronized (mReporters) { reporters = new LinkedList<>(mReporters); } reporters.stream() .forEach( reporter -> { reporter.reportStatus(pw); pw.println(); }); report = sw.toString(); mLogger.log(mLevel, report); } // end of handleTimeout(TimerEvent) // // end of TimerTaskListener Interface Implementation //----------------------------------------------------------- //----------------------------------------------------------- // Get methods. // /** * Returns the {@code java.util.logging.Logger} to which * the status report is written. * @return the {@code java.util.logging.Logger} to which * the status report is written. */ public synchronized Logger getLogger() { return (mLogger); } // end of getLogger() /** * Returns the {@code java.util.logging.Level} at which * the status report is logged. * @return the {@code java.util.logging.Level} at which * the status report is logged. */ public synchronized Level getLevel() { return (mLevel); } // end of getLevel() /** * Returns the current status report frequency in * milliseconds. * @return the current status report frequency in * milliseconds. */ public synchronized ReportFrequency getReportFrequency() { return (mFrequency); } // end of getReportFrequency() /** * Returns the "dump threads" flag. If this flag is * {@code true}, then the active threds are * listed in the report. This flag is {@code false} by * default. * @return the "dump threads" flag. */ public synchronized boolean getDumpThreads() { return (mDumpThreadsFlag); } // end of getDumpThreads() // // end of Get methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set methods. // /** * Sets the {@code java.util.logging.Logger}. The * status report is written to this logger. * @param logger write the status report to this log. */ public synchronized void setLogger(final Logger logger) { mLogger = logger; } // end of setLogger(Logger) /** * Sets the {@code java.util.logging.Level} at which * the status report is logged. * @param level log the status report at this level. * @throws NullPointerException * if {@code level} is {@code null}. */ public synchronized void setLevel(final Level level) { mLevel = Objects.requireNonNull(level, "null level"); } // end of setLevel(Level) /** * Sets the status report frequency. The allowed frequencies * are: 5 minute, 10 minute, 15 minute, 20 minute, 30 minute * and hourly. The default is 15 minute. If the frequency is * none of the above, then an * {@code java.lang.IllegalArgumentException} is thrown. * @param frequency the status report frequency. */ public synchronized void setReportFrequency(final ReportFrequency frequency) { // If the frequency has changed, then stop the timer and // set it to the new rate. if (mFrequency != frequency) { mFrequency = frequency; if (mReportTimer != null) { // Once a TimerTask object is cancelled, you // have to throw it away and create a new one. // TimerTasks can't be reused. mReportTimer.cancel(); mReportTimer = null; } // Now start the periodic report timer. startReportTimer(); } } // end of setReportFrequency(ReportFrequency) /** * Sets the "dump threads" flag to the given value. * If this flag is {@code true}, then the active threads * are listed in the report. * @param flag set the "dump threads" flag to this value. */ public synchronized void setDumpThreads(final boolean flag) { mDumpThreadsFlag = flag; } // end of setDumpThreads(boolean) // // end of Set methods. //----------------------------------------------------------- /** * Sets the application's name, version and build date. This * information is placed at the start of each status report * log message. * @param name the application's name. * @param version the application's version. * @param buildDate the application's build date. * @param boilerPlate usually a copyright statement. * @exception IllegalStateException * if the application information is already set. */ public synchronized void setApplicationInfo(final String name, final String version, final Date buildDate, final String boilerPlate) { if (mInitFlag) { throw ( new IllegalStateException( "app info already set")); } else { final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); final String report; mInitFlag = true; mAppName = name; mVersion = version; mBuildDate = new Date(buildDate.getTime()); mBoilerPlate = boilerPlate; pw.println( "=============================================" + "===================="); logHeader(pw); report = sw.toString(); mLogger.info(report); } } // end of setApplicationInfo(String, String, Date, String) /** * Registers a status reporter. If the reporter is already * registered, then it is not added again. *

* Note: When the status report is generated, * reporters will be called in the order they * register. * @param reporter register this status reporter. */ public void register(final StatusReporter reporter) { synchronized (mReporters) { if (!mReporters.contains(reporter)) { mReporters.add(reporter); } } } // end of register(StatusReporter) /** * Deregisters a status reporter. If the reporter is not * registered, then nothing is done. * @param reporter deregister this reporter. */ public void deregister(final StatusReporter reporter) { synchronized (mReporters) { mReporters.remove(reporter); } } // end of deregister(StatusReporter) /** * Returns the singleton {@link StatusReport} instance. * @return the singleton {@link StatusReport} instance. */ public static StatusReport getsInstance() { sCtorMutex.lock(); try { if (sInstance == null) { // Store away this single instance. sInstance = new StatusReport(); sInstance.startReportTimer(); } } finally { sCtorMutex.unlock(); } return (sInstance); } // end of getInstance() /** * Given a millisecond duration, generate a string that * reports the duration in human-readable form. * @param delta The millisecond duration. * @return the textual representation of a duration. */ public static String formatTime(final long delta) { final TimeUnit[] timeUnits = TimeUnit.values(); final int numUnits = timeUnits.length; int i; boolean startFlag; long scalar; long delta2 = delta; final StringBuilder retval = new StringBuilder(); // Do days, hours, minutes, seconds, and milliseconds. // Skip micro- and nonoseconds. for (i = (numUnits -1), startFlag = false; i >= 2; --i) { // Figure out the number of these time units // in the delta. scalar = timeUnits[i].convert( delta2, TimeUnit.MILLISECONDS); delta2 -= TimeUnit.MILLISECONDS.convert( scalar, timeUnits[i]); // Print out this time unit but only if it is not // zero or a non-zero unit has already been printed. if (scalar > 0 || startFlag) { // If the buffer already constains output, // then make sure to put a ", " before this // new unit. if (!startFlag) { startFlag = true; } else { retval.append(", "); } retval.append(scalar) .append(' ') .append((timeUnits[i].name()).toLowerCase()); } } // If nothing was printed out, then print out // 0 millisecs. if (!startFlag) { retval.append("0 millisecs"); } return (retval.toString()); } // end of FormatTime(long) // Place the application information in the log. private void logHeader(final PrintWriter pw) { pw.print(mAppName); pw.print(" v. "); pw.print(mVersion); if (mBuildDate != null) { pw.format( " (build %1$tm/%1$td/%1$tY at %1$tH:%1$tM:%1$tS)%n", mBuildDate); } if (mBoilerPlate != null) { pw.println(); pw.println(mBoilerPlate); } pw.println(); pw.print("Current working directory is "); pw.print(System.getProperty("user.dir")); pw.println("."); pw.print("Class path is "); pw.print(System.getProperty("java.class.path")); pw.println("."); } // end of logHeader(PrintWriter) // Create the report timer task and start it running. private void startReportTimer() { final long frequency = mFrequency.getFrequency(); final long reportDelay = (frequency - (System.currentTimeMillis() % frequency)); mReportTimer = new TimerTask(this); sTimer.scheduleAtFixedRate(mReportTimer, reportDelay, frequency); } // end of startReportTimer() // Count up all the active threads in this JVM. private void outputThreads(final PrintWriter pw, final ThreadMXBean threadBean) { final long[] threadIds = threadBean.getAllThreadIds(); final int numThreads = threadIds.length; int index; ThreadInfo threadInfo; // Output thread header. pw.println("Active threads:"); pw.println(THREAD_HEADER_2); pw.println(THREAD_HEADER); pw.println(THREAD_HEADER_2); for (index = 0; index < numThreads; ++index) { threadInfo = threadBean.getThreadInfo(threadIds[index]); // null threadInfo means the thread died between // the getAllThreadIds() call and // getThreadInfo(). if (threadInfo != null) { pw.format("| %20s | %10d | %15s |%n", threadInfo.getThreadName(), threadIds[index], threadInfo.getThreadState()) .println(THREAD_HEADER_2); } } } // end of outputThreads(PrintWriter, ThreadMXBean) } // end of class StatusReport





© 2015 - 2025 Weber Informatics LLC | Privacy Policy