/*license*\
XBN-Java Library
Copyright (c) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com)
This software is dual-licensed under the:
- Lesser General Public License (LGPL) version 3.0 or, at your option, any later version;
- Apache Software License (ASL) version 2.0.
Either license may be applied at your discretion. More information may be found at
- http://en.wikipedia.org/wiki/Multi-licensing.
The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at:
- LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
- ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
\*license*/
package com.github.xbn.testdev;
import java.util.Objects;
import com.github.xbn.io.IOUtil;
import com.github.xbn.number.NewIntInRangeFor;
import com.github.xbn.testdev.GetFromCommandLineAtIndex;
import java.io.PrintWriter;
import com.github.xbn.util.tuple.ThreeTuple;
import com.github.xbn.lang.CrashIfObject;
import com.github.xbn.text.CrashIfString;
import java.util.Locale;
import java.text.NumberFormat;
/**
<p>For benchmarking the speed of a process to the nano-second, and comparing it against another.</p>
{@.codelet.and.out com.github.xbn.examples.testdev.Time3StringConcatVsAppend(100000)%eliminateCommentBlocksAndPackageDecl()}
{@.codelet.and.out com.github.xbn.examples.testdev.TimeIteratorVsIndexIntegerList(1000000)%eliminateCommentBlocksAndPackageDecl()}
* @since 0.1.0
* @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://xbnjava.aliteralmind.com">{@code http://xbnjava.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/xbnjava">{@code https://github.com/aliteralmind/xbnjava}</a>
**/
public class TimedTest {
private static NumberFormat numFmt;
private final String name;
private long startTime = -1;
private long endTime = -1;
private static PrintWriter writer;
/**
<p>The default number of digits to show after the decimal point in the "percentage faster by" message--Equal to {@code 3}</p>
* @see #getTestABTestNanoDifferenceMsg(TimedTest)
*/
public static final int DECIMAL_PLACES_DEFAULT = 3;
/**
<p>Create a new instance with a descriptive name.</p>
<p>If getWriter() is {@code null} (which only occurs in the first {@code TimedTest} instance), this calls<ol>
<li><code>TimedTest.{@link #setLocale(Locale) setLocale}({@link java.util.Locale Locale}.{@link java.util.Locale#US US})</code></li>
<li><code>TimedTest.setWriter(new {@link java.io.PrintWriter#PrintWriter(OutputStream) PrintWriter}(System.out))</code></li>
</ol></p>
* @param name Descriptive name used in output. May not be {@code null} or empty. Get with {@link #getName() getName}{@code ()}.
*/
public TimedTest(String name) {
if(numFmt == null) {
TimedTest.setLocale(Locale.US);
TimedTest.setWriter(new PrintWriter(System.out));
}
CrashIfString.nullEmpty(name, "name", null);
this.name = name;
}
/**
<p>Descriptive name of this test, used in output strings.</p>
* @see #TimedTest(String)
*/
public String getName() {
return name;
}
/**
<p>Output a "starting test" message, then start it.</p>
* @see #declareStart()
* @see #declareEndWithOutput()
* @see #declareEndThenStartNextWithOutput(TimedTest)
* @exception RuntimeException If attempting to write the message fails for any reason.
*/
public void declareStartWithOutput() {
try {
getWriter().println("Starting test \"" + getName() + "\"");
getWriter().flush();
} catch(RuntimeException rx) {
throw new RuntimeException("Attempting getWriter().write(...)", rx);
}
declareStart();
}
/**
<p>End this test, output a "test ended, starting next test" message, then start the next.</p>
<p>This<ol>
<li>Calls {@link #declareEnd() declareEnd}{@code ()}</li>
<li>Outputs a message ia {@link #getWriter() getWriter}{@code ()}, its contents beginning with {@link #getTestTookXNanosMsg() getTestTookXNanosMsg}{@code ()}.</li>
<li>Calls <code>next_test.{@link #declareStart() declareStart}()</code></li>
</ol></p>
<p></p>
* @param next_test May not be {@code null}.
* @see #declareEndWithOutput()
* @exception RuntimeException If attempting to write the message fails for any reason.
*/
public void declareEndThenStartNextWithOutput(TimedTest next_test) {
declareEnd();
try {
getWriter().println(getTestTookXNanosMsg() + ". Starting test \"" + next_test.getName() + "\"");
getWriter().flush();
} catch(RuntimeException rx) {
Objects.requireNonNull(next_test, "next_test");
throw new RuntimeException("Attempting getWriter().write(...)", rx);
}
next_test.declareStart();
}
/**
<p>End the test, and print a "test ended" message.</p>
<p>This<ol>
<li>Calls {@link #declareEnd() declareEnd}{@code ()}</li>
<li>Outputs {@link #getTestTookXNanosMsg() getTestTookXNanosMsg}{@code ()} with {@link #getWriter() getWriter}{@code ()}.</li>
</ol></p>
* @exception RuntimeException If attempting to write the message fails for any reason.
*/
public void declareEndWithOutput() {
declareEnd();
try {
getWriter().println(getTestTookXNanosMsg());
getWriter().flush();
} catch(RuntimeException rx) {
throw new RuntimeException("Attempting getWriter().write(...)", rx);
}
}
/**
<p>Start the test.</p>
<p>This sets {@link #getStart() getStart}{@code ()} to <code>{@link java.lang.System System}.{@link java.lang.System#nanoTime() nanoTime}</code>.</p>
* @see #declareEnd()
* @see #declareStartWithOutput()
* @exception IllegalStateException If {@link #wasStarted() wasStarted}{@code ()} is {@code true}. (The test is not started until after this {@code wasStarted()} check.)
*/
public void declareStart() {
if(wasStarted()) {
throw new IllegalStateException("wasStarted() is true.");
}
startTime = System.nanoTime();
}
/**
<p>End the test.</p>
<p>This sets {@link #getEnd() getEnd}{@code ()} to <code>{@link java.lang.System System}.{@link java.lang.System#nanoTime() nanoTime}</code>.</p>
* @see #declareStart()
* @see #declareEndWithOutput()
* @see #declareEndThenStartNextWithOutput(TimedTest)
* @exception IllegalStateException If {@link #wasStarted() wasStarted}{@code ()} is {@code false} or {@link #wasEnded() wasEnded}{@code ()} is {@code true}. (These {@code was*} tests are not done until after the test is stopped. But {@code getEnd()} is not set to this value until these tests pass.)
*/
public void declareEnd() {
long end2 = System.nanoTime();
if(!wasStarted()) {
throw new IllegalStateException("wasStarted() is false.");
}
if(wasEnded()) {
throw new IllegalStateException("wasEnded() is true.");
}
endTime = end2;
}
/**
<p>The nanosecond that the test started.</p>
* @return The nanosecond the test started, or {@code -1} if {@link #wasStarted() wasStarted}{@code ()} is {@code false}.
* @see #getEnd()
* @see #declareStart()
*/
public long getStart() {
return startTime;
}
/**
<p>Was the test started?.</p>
* @return <code>({@link #getStart() getStart}() != -1)</code>
* @see #wasEnded()
*/
public boolean wasStarted() {
return (getStart() != -1);
}
/**
<p>Was the test ended?.</p>
* @return <code>({@link #getEnd() getEnd}() != -1)</code>
* @see #wasStarted()
*/
public boolean wasEnded() {
return (getEnd() != -1);
}
/**
<p>The nanosecond that the test ended.</p>
* @return The nanosecond the test ended, or {@code -1} if {@link #wasEnded() wasEnded}{@code ()} is {@code false}.
* @see #getStart()
* @see #declareEnd()
*/
public long getEnd() {
return endTime;
}
/**
<p>Get the nanoseconds from test start-to-finish.</p>
* @return <code>({@link #getEnd() getEnd}() - {@link #getStart() getStart}())</code>
* @exception IllegalStateException If {@link #wasEnded() wasEnded}{@code ()} is {@code false}.
*/
public long getDuration() {
if(!wasEnded()) {
throw new IllegalStateException("wasEnded() is false.");
}
return (getEnd() - getStart());
}
public static final Locale[] getAvailableLocales() {
return numFmt.getAvailableLocales();
}
public static final PrintWriter getWriter() {
return writer;
}
/**
<p>Was this test faster than another?.</p>
* @return <code>({@link #longCompareTo(TimedTest) longCompareTo}(to_compareTo) < 0)</code>
*/
public boolean didBeat(TimedTest to_compareTo) {
return (longCompareTo(to_compareTo) < 0); //this < to_compareTo
}
/**
<p>Get the between this test and another's duration.</p>
* @param to_compareTo May not be {@code null}, and its test must be {@link #wasEnded() ended}.
* @return <code>{@link java.lang.Long Long}.{@link java.lang.Long#compare(long, long) compare}({@link #getDuration() getDuration}(), to_compareTo.getDuration())</code>
* @exception IllegalStateException If {@link #wasEnded() wasEnded}{@code ()} is {@code false}.
* @see #didBeat(TimedTest)
*/
public long longCompareTo(TimedTest to_compareTo) {
try {
return Long.compare(getDuration(), to_compareTo.getDuration());
} catch(RuntimeException rx) {
throw CrashIfObject.nullOrReturnCause(to_compareTo, "to_compareTo", null, rx);
}
}
/**
<p>A basic message to display after this test ends.</p>
* @return <code>TimedTest.{@link #getTestTookXNanosMsg(TimedTest) getTestTookXNanosMsg}(this)</code>
*/
public String getTestTookXNanosMsg() {
return TimedTest.getTestTookXNanosMsg(this);
}
/**
<p>Determines the winner, loser, and the time-difference between them.</p>
* @return <code>TimedTest.{@link #getWinnerDifferenceLoser(TimedTest, TimedTest) getWinnerDifferenceLoser}(this, to_compareTo)</code>
*/
public ThreeTuple<TimedTest,Long,TimedTest> getWinnerDifferenceLoser(TimedTest to_compareTo) {
return TimedTest.getWinnerDifferenceLoser(this, to_compareTo);
}
/**
<p>A message stating which of two tests was faster, and by what percentage.</p>
* @return <code>{@link #getTestABTestNanoDifferenceMsg(TimedTest, int) getTestABTestNanoDifferenceMsg}(to_compareTo, {@link #DECIMAL_PLACES_DEFAULT})</code>
*/
public String getTestABTestNanoDifferenceMsg(TimedTest to_compareTo) {
return getTestABTestNanoDifferenceMsg(to_compareTo, DECIMAL_PLACES_DEFAULT);
}
/**
<p>A message stating which of two tests was faster, and by what percentage.</p>
* @return <code>TimedTest.{@link #getTestABTestNanoDifferenceMsg(TimedTest, TimedTest, int) getTestABTestNanoDifferenceMsg}(this, to_compareTo, decimal_places)</code>
*/
public String getTestABTestNanoDifferenceMsg(TimedTest to_compareTo, int decimal_places) {
return TimedTest.getTestABTestNanoDifferenceMsg(this, to_compareTo, decimal_places);
}
/**
<p>A message stating how long a test took.</p>
* @param test A test that has ended. May not be {@code null}.
* @see #getTestTookXNanosMsg()
* @see #getTestABTestNanoDifferenceMsg(TimedTest, TimedTest, int) getTestABTestNanoDifferenceMsg(tt,tt,i)
*/
public static final String getTestTookXNanosMsg(TimedTest test) {
try {
return "Test \"" + test.getName() + "\" took " + numFmt.format(test.getDuration()) + " nanoseconds";
} catch(RuntimeException rx) {
throw CrashIfObject.nullOrReturnCause(test, "test", null, rx);
}
}
/**
<p>Determines the winner, loser, and the time-difference between them.</p>
* @param test_a May not be {@code null} and must be {@link #wasEnded() ended}.
* @param test_b May not be {@code null} and must be ended.
* @return An tuple containing the following three objects in order: The faster test, the nanosecond difference between them, and the slower tast.
* @exception IllegalStateException If this test is not ended.
* @see #getWinnerDifferenceLoser(TimedTest, TimedTest) getWinnerDifferenceLoser(tt,tt)
* @see #getTestABTestNanoDifferenceMsg(TimedTest, TimedTest, int) getTestABTestNanoDifferenceMsg(tt,tt,i)
*/
public static final ThreeTuple<TimedTest,Long,TimedTest> getWinnerDifferenceLoser(TimedTest test_a, TimedTest test_b) {
long diff = -1;
TimedTest winner = null;
TimedTest loser = null;
try {
if(test_a.getDuration() > test_b.getDuration()) {
winner = test_b;
diff = test_a.getDuration() - test_b.getDuration();
loser = test_a;
} else {
winner = test_a;
diff = test_b.getDuration() - test_a.getDuration();
loser = test_b;
}
} catch(RuntimeException rx) {
Objects.requireNonNull(test_a, "test_a");
throw CrashIfObject.nullOrReturnCause(test_b, "test_b", null, rx);
}
return (new ThreeTuple<TimedTest,Long,TimedTest>(winner, diff, loser));
}
/**
<p>A message stating which of two tests was faster, and by what percentage.</p>
* @return <code>{@link #getTestABTestNanoDifferenceMsg(TimedTest, TimedTest, int) getTestABTestNanoDifferenceMsg}(test_a, test_b, {@link #DECIMAL_PLACES_DEFAULT})</code>
*/
public static final String getTestABTestNanoDifferenceMsg(TimedTest test_a, TimedTest test_b) {
return getTestABTestNanoDifferenceMsg(test_a, test_b, DECIMAL_PLACES_DEFAULT);
}
/**
<p>A message stating which of two tests was faster, and by what percentage.</p>
* @param test_a May not be {@code null}, and must be {@link #wasEnded() ended}.
* @param test_b May not be {@code null}, and must be ended.
* @param decimal_places The number of digits to display to the right of the decimal point. May not be less than zero, and must be valid for <code>String.{@link java.lang.String#format(String, Object...) format}(s,o...)</code> (for use between {@code "%."} and {@code "f"}).
* @see #getWinnerDifferenceLoser(TimedTest, TimedTest) getWinnerDifferenceLoser(tt,tt)
* @see #getTestABTestNanoDifferenceMsg(TimedTest) getTestABTestNanoDifferenceMsg(tt)
* @see #getTestABTestNanoDifferenceMsg(TimedTest, int) getTestABTestNanoDifferenceMsg(tt,i)
* @see #getTestABTestNanoDifferenceMsg(TimedTest, TimedTest) getTestABTestNanoDifferenceMsg(tt,tt)
* @see #getTestTookXNanosMsg(TimedTest) getTestTookXNanosMsg(tt)
*/
public static final String getTestABTestNanoDifferenceMsg(TimedTest test_a, TimedTest test_b, int decimal_places) {
ThreeTuple<TimedTest,Long,TimedTest> winnerDiffLoser = getWinnerDifferenceLoser(test_a, test_b);
double dPct = 100.00 -
(winnerDiffLoser.get1().getDuration() * 100.0 /
winnerDiffLoser.get3().getDuration() + 0.5);
String pctFrmtd;
try {
pctFrmtd = String.format("%." + decimal_places + "f", dPct);
} catch(RuntimeException rx) {
throw new RuntimeException("Attempting String.format(\"%.\" + decimal_places + \"f\", ...), decimal_places=" + decimal_places);
}
return "FASTER: Test \"" + winnerDiffLoser.get1().getName() + "\", by " + numFmt.format(winnerDiffLoser.get2()) + " nanoseconds (" + pctFrmtd + "%)";
}
/**
<p>Set the localization used for displaying the nanoseconds.</p>
* @param locale May not be {@code null}. Get with {@link #getAvailableLocales() getAvailableLocales}{@code ()}
* @see #setWriter(PrintWriter)
*/
public static final void setLocale(Locale locale) {
Objects.requireNonNull(locale, "locale");
numFmt = NumberFormat.getNumberInstance(locale);
}
/**
<p>Set the writer for printing messages.</p>
* @param writer May not be {@code null}, and <code>writer.{@link java.io.PrintWriter#checkError() checkError}()</code> must be {@code false}. Get with {@link #getWriter() getWriter}{@code ()}
* @see #setLocale(Locale)
*/
public static final void setWriter(PrintWriter writer) {
IOUtil.crashIfPrintWriterError(writer, "writer");
TimedTest.writer = writer;
}
/**
<p>Get the number of test iterations as provided in the command line.</p>
* @return <code>{@link com.github.xbn.testdev.GetFromCommandLineAtIndex GetFromCommandLineAtIndex}.{@link com.github.xbn.testdev.GetFromCommandLineAtIndex#number(String[], int, IntInRange, String, Appendable) number}(cmd_lineParams, test_countIdx, {@link com.github.xbn.number.NewIntInRangeFor NewIntInRangeFor}.{@link com.github.xbn.number.NewIntInRangeFor#min(Invert, int, String) min}(1, null))</code>
*/
public static final int getTestCountFromCmdLine(String[] cmd_lineParams, int test_countIdx) {
return GetFromCommandLineAtIndex.number(cmd_lineParams, test_countIdx, NewIntInRangeFor.min(null, 1, null), "[test count]", null);
}
}