
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:
*
* -
* FIVE_MINUTE: generate a report every five minutes on
* the five minute.
*
* -
* TEN_MINUTE: generate a report every ten minutes on the
* ten minute;
*
* -
* FIFTEEN_MINUTE: generate a report every fifteen
* minutes on the quarter hour.
*
* -
* TWENTY_MINUTE: generate a report every twenty minutes
* on the twenty minute.
*
* -
* THIRTY_MINUTE: generate a report every thirty minutes
* on the half hour.
*
* -
* HOURLY: generate a report every hour on the hour.
*
*
*/
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