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,178 @@ 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 Duration duration = Duration .between (splits .get (i ).getStartTime (), splits .get (i +1 ).getStartTime ());
868+ splits .get (i ).setDuration (timeUnit == TimeUnit .NANOSECONDS ? duration .toNanos () : duration .toMillis ());
869+ }
870+
871+ }
872+
873+ /**
874+ * <p>
875+ * Stops the watch and returns the splits report.
876+ * This report contains the elapsed time (on milliseconds) between each split
877+ * </p>
878+ *
879+ * @return the splits report
880+ */
881+ public String getReport () {
882+ return getReport (TimeUnit .MILLISECONDS );
883+ }
884+
885+ /**
886+ * <p>
887+ * Stops the watch and returns the splits report.
888+ * This report contains the elapsed time (on nanoseconds) between each split
889+ * </p>
890+ *
891+ * @return the splits report
892+ */
893+ public String getNanoReport () {
894+ return getReport (TimeUnit .NANOSECONDS );
895+ }
896+
897+ /**
898+ * <p>
899+ * Stops the watch and returns the splits report.
900+ * This report contains the elapsed time between each split
901+ * </p>
902+ *
903+ * @param timeUnit the unit of time, not null. Any value will calculate with milliseconds precision unless
904+ * {@code TimeUnit.NANOSECONDS} is specified.
905+ * @return the splits report
906+ */
907+ private String getReport (TimeUnit timeUnit ) {
908+ final StringBuilder report = new StringBuilder ();
909+
910+ String duration ;
911+ for (final Split split : getProcessedSplits (timeUnit )) {
912+ report .append (System .lineSeparator ());
913+ report .append (split .getLabel ()).append (StringUtils .SPACE );
914+
915+ if (timeUnit == TimeUnit .NANOSECONDS ) {
916+ duration = String .valueOf (split .getDuration ());
917+ } else {
918+ duration = DurationFormatUtils .formatDurationHMS (split .getDuration ());
919+ }
920+
921+ report .append (duration );
922+ }
923+
924+ return report .toString ();
925+ }
926+
927+ /**
928+ * Class to store details of each split
929+ */
930+ protected static class Split {
931+
932+ /**
933+ * The start time of this split
934+ */
935+ private Instant startTime = Instant .now ();
936+
937+ /**
938+ * The duration (time took) on this split
939+ * This field is filled when user calls getSplits() or tries to print the splits report
940+ */
941+ private long duration ;
942+
943+ /*
944+ * The label for this split
945+ */
946+ private String label ;
947+
948+ /**
949+ * @param label Label for this split
950+ */
951+ public Split (String label ) {
952+ this .label = label ;
953+ }
954+
955+ /**
956+ * <p>
957+ * Get the timestamp when this split was created
958+ * </p>
959+ *
960+ * @return startTime
961+ */
962+ public Instant getStartTime () {
963+ return startTime ;
964+ }
965+
966+ /**
967+ * <p>
968+ * Get the label of this split
969+ * </p>
970+ *
971+ * @return label
972+ */
973+ public String getLabel () {
974+ return label ;
975+ }
976+
977+ /**
978+ * Duration of this split
979+ * @return duration (time on ms or nano)
980+ */
981+ public long getDuration () {
982+ return duration ;
983+ }
984+
985+ /**
986+ * Set the duration of this split
987+ * @param duration time (on ms or nano)
988+ */
989+ private void setDuration (long duration ) {
990+ this .duration = duration ;
991+ }
992+ }
993+
749994}
0 commit comments