1919
2020import java .time .Duration ;
2121import java .time .Instant ;
22+ import java .util .ArrayList ;
23+ import java .util .List ;
2224import java .util .Objects ;
2325import java .util .concurrent .TimeUnit ;
2426import java .util .function .Supplier ;
@@ -248,6 +250,11 @@ public static StopWatch createStarted() {
248250 */
249251 private long stopTimeNanos ;
250252
253+ /**
254+ * The list of splits
255+ */
256+ private List <Split > splits ;
257+
251258 /**
252259 * Constructs a new instance.
253260 */
@@ -626,6 +633,59 @@ public void split() {
626633 splitState = SplitState .SPLIT ;
627634 }
628635
636+ /**
637+ * <p>
638+ * Splits the time to track the elapsed time between two consecutive {@code split()} calls.
639+ * The label specified is used to identify each split
640+ * </p>
641+ *
642+ * <p>
643+ * After calling {@link #stop()}, we can call {@link #getReport()} to have a report with all time between each {@code split()} call, example:
644+ * </p>
645+ *
646+ * <pre>
647+ * 1 00:14:00.000
648+ * 2 00:02:00.000
649+ * 3 00:04:00.000
650+ * </pre>
651+ *
652+ * @param label A number to identify this split
653+ *
654+ * @throws IllegalStateException
655+ * if the StopWatch is not running.
656+ */
657+ public void split (int label ) {
658+ split (String .valueOf (label ));
659+ }
660+
661+ /**
662+ * <p>
663+ * Splits the time to track the elapsed time between two consecutive {@code split()} calls.
664+ * The label specified is used to identify each split
665+ * </p>
666+ *
667+ * <p>
668+ * After calling {@link #stop()}, we can call {@link #getReport()} to have a report with all time between each {@code split()} call, example:
669+ * </p>
670+ *
671+ * <pre>
672+ * Baking cookies 00:14:00.000
673+ * Serving 00:02:00.000
674+ * Eating 00:04:00.000
675+ * </pre>
676+ *
677+ * @param label A message for string presentation.
678+ *
679+ * @throws IllegalStateException
680+ * if the StopWatch is not running.
681+ */
682+ public void split (String label ) {
683+ if (this .runningState != State .RUNNING ) {
684+ throw new IllegalStateException ("Stopwatch is not running." );
685+ }
686+ splits .add (new Split (label ));
687+ }
688+
629689 /**
630690 * Starts this StopWatch.
631691 *
@@ -645,6 +705,7 @@ public void start() {
645705 startTimeNanos = System .nanoTime ();
646706 startInstant = Instant .now ();
647707 runningState = State .RUNNING ;
708+ splits = new ArrayList <>();
648709 }
649710
650711 /**
@@ -674,10 +735,20 @@ public void stop() {
674735 if (runningState == State .RUNNING ) {
675736 stopTimeNanos = System .nanoTime ();
676737 stopInstant = Instant .now ();
738+ split (StringUtils .EMPTY );
677739 }
678740 runningState = State .STOPPED ;
679741 }
680742
743+ /**
744+ * Stops the watch if necessary
745+ */
746+ private void stopIfNecessary () {
747+ if (this .runningState == State .RUNNING || this .runningState == State .SUSPENDED ) {
748+ stop ();
749+ }
750+ }
751+
681752 /**
682753 * Suspends this StopWatch for later resumption.
683754 *
@@ -746,4 +817,182 @@ public void unsplit() {
746817 splitState = SplitState .UNSPLIT ;
747818 }
748819
820+ /**
821+ * Stops the watch and returns the list of splits with duration on each split (using milliseconds)
822+ * @return list of splits
823+ */
824+ public List <Split > getProcessedSplits () {
825+ return getProcessedSplits (TimeUnit .MILLISECONDS );
826+ }
827+
828+ /**
829+ * Stops the watch and returns the list of splits with duration on each split (using nanoseconds)
830+ * @return list of splits
831+ */
832+ public List <Split > getNanoProcessedSplits () {
833+ return getProcessedSplits (TimeUnit .NANOSECONDS );
834+ }
835+
836+ /**
837+ * Stops the watch and returns the list of splits with duration on each split
838+ *
839+ * @param timeUnit the unit of time, not null. Any value will calculate with milliseconds precision unless
840+ * {@code TimeUnit.NANOSECONDS} is specified.
841+ * @return list of splits
842+ */
843+ public List <Split > getProcessedSplits (TimeUnit timeUnit ) {
844+ stopIfNecessary ();
845+ processSplits (timeUnit );
846+ final List <Split > result = new ArrayList <>(splits );
847+
848+ // we remove the last split because it's an internal and automatic split
849+ result .remove (result .size () - 1 );
850+
851+ return result ;
852+ }
853+
854+ /**
855+ * Fill durations (time took) on each split
856+ *
857+ * @param timeUnit the unit of time, not null. Any value will calculate with milliseconds precision unless
858+ * {@code TimeUnit.NANOSECONDS} is specified.
859+ */
860+ private void processSplits (TimeUnit timeUnit ) {
861+ // we need at least 2 splits to calculate the elapsed time
862+ if (splits .size () < 2 ) {
863+ return ;
864+ }
865+
866+ for (int i = 0 ; i < splits .size () - 1 ; i ++) {
867+ final long durationNanos = splits .get (i + 1 ).getStartNanoTime () - splits .get (i ).getStartNanoTime ();
868+ final long duration = (timeUnit == TimeUnit .NANOSECONDS )
869+ ? durationNanos
870+ : TimeUnit .MILLISECONDS .convert (durationNanos , TimeUnit .NANOSECONDS );
871+ splits .get (i ).setDuration (duration );
872+ }
873+
874+ }
875+
876+ /**
877+ * <p>
878+ * Stops the watch and returns the splits report.
879+ * This report contains the elapsed time (on milliseconds) between each split
880+ * </p>
881+ *
882+ * @return the splits report
883+ */
884+ public String getReport () {
885+ return getReport (TimeUnit .MILLISECONDS );
886+ }
887+
888+ /**
889+ * <p>
890+ * Stops the watch and returns the splits report.
891+ * This report contains the elapsed time (on nanoseconds) between each split
892+ * </p>
893+ *
894+ * @return the splits report
895+ */
896+ public String getNanoReport () {
897+ return getReport (TimeUnit .NANOSECONDS );
898+ }
899+
900+ /**
901+ * <p>
902+ * Stops the watch and returns the splits report.
903+ * This report contains the elapsed time between each split
904+ * </p>
905+ *
906+ * @param timeUnit the unit of time, not null. Any value will calculate with milliseconds precision unless
907+ * {@code TimeUnit.NANOSECONDS} is specified.
908+ * @return the splits report
909+ */
910+ private String getReport (TimeUnit timeUnit ) {
911+ final StringBuilder report = new StringBuilder ();
912+
913+ String duration ;
914+ for (final Split split : getProcessedSplits (timeUnit )) {
915+ report .append (System .lineSeparator ());
916+ report .append (split .getLabel ()).append (StringUtils .SPACE );
917+
918+ if (timeUnit == TimeUnit .NANOSECONDS ) {
919+ duration = String .valueOf (split .getDuration ());
920+ } else {
921+ duration = DurationFormatUtils .formatDurationHMS (split .getDuration ());
922+ }
923+
924+ report .append (duration );
925+ }
926+
927+ return report .toString ();
928+ }
929+
930+ /**
931+ * Class to store details of each split
932+ */
933+ protected static class Split {
934+
935+ /**
936+ * The start nano time of this split
937+ */
938+ private long startNanoTime = System .nanoTime ();
939+
940+ /**
941+ * The duration (time took) on this split
942+ * This field is filled when user calls getSplits() or tries to print the splits report
943+ */
944+ private long duration ;
945+
946+ /*
947+ * The label for this split
948+ */
949+ private String label ;
950+
951+ /**
952+ * Constructor with label
953+ * @param label Label for this split
954+ */
955+ public Split (String label ) {
956+ this .label = label ;
957+ }
958+
959+ /**
960+ * <p>
961+ * Get the timestamp when this split was created
962+ * </p>
963+ *
964+ * @return startNanoTime
965+ */
966+ public long getStartNanoTime () {
967+ return startNanoTime ;
968+ }
969+
970+ /**
971+ * <p>
972+ * Get the label of this split
973+ * </p>
974+ *
975+ * @return label
976+ */
977+ public String getLabel () {
978+ return label ;
979+ }
980+
981+ /**
982+ * Duration of this split
983+ * @return duration (time on ms or nano)
984+ */
985+ public long getDuration () {
986+ return duration ;
987+ }
988+
989+ /**
990+ * Set the duration of this split
991+ * @param duration time (on ms or nano)
992+ */
993+ private void setDuration (long duration ) {
994+ this .duration = duration ;
995+ }
996+ }
997+
749998}
0 commit comments