1414 */
1515package net .rptools .lib ;
1616
17+ import java .awt .EventQueue ;
1718import java .util .ArrayList ;
1819import java .util .LinkedHashMap ;
1920import java .util .List ;
2021import java .util .Map ;
22+ import java .util .concurrent .TimeUnit ;
2123import net .rptools .maptool .client .AppState ;
2224import net .rptools .maptool .client .MapTool ;
25+ import org .apache .logging .log4j .LogManager ;
26+ import org .apache .logging .log4j .Logger ;
2327
2428public class CodeTimer {
2529 private static final ThreadLocal <CodeTimer > ROOT_TIMER =
2630 ThreadLocal .withInitial (() -> new CodeTimer ("" ));
2731 private static final ThreadLocal <List <CodeTimer >> timerStack =
2832 ThreadLocal .withInitial (ArrayList ::new );
33+ private static final Logger log = LogManager .getLogger (CodeTimer .class );
2934
3035 @ FunctionalInterface
3136 public interface TimedSection <Ex extends Throwable > {
@@ -49,11 +54,7 @@ public static <Ex extends Exception> void using(String name, TimedSection<Ex> ca
4954 final var lastTimer = stack .removeLast ();
5055 assert lastTimer == timer : "Timer stack is corrupted" ;
5156
52- if (timer .isEnabled ()) {
53- String results = timer .toString ();
54- MapTool .getProfilingNoteFrame ().addText (results );
55- }
56- timer .clear ();
57+ timer .complete ();
5758 }
5859 }
5960
@@ -62,23 +63,38 @@ public static CodeTimer get() {
6263 return stack .isEmpty () ? ROOT_TIMER .get () : stack .getLast ();
6364 }
6465
65- private final Map <String , Integer > counterMap = new LinkedHashMap <>();
66- private final Map <String , Timer > timeMap = new LinkedHashMap <>();
66+ private enum TimerEventType {
67+ Start ,
68+ Stop
69+ }
70+
71+ private record TimerEvent (TimerEventType type , long timeNs , String id , Object [] parameters ) {}
72+
73+ private record CounterEvent (long amount , String id ) {}
74+
75+ private ArrayList <TimerEvent > timerEvents = new ArrayList <>(100 );
76+ private ArrayList <CounterEvent > counterEvents = new ArrayList <>(100 );
77+
6778 private final String name ;
6879 private long threshold = 1 ;
80+ private TimeUnit reportingUnit = TimeUnit .MILLISECONDS ;
6981 private boolean enabled ;
7082
71- public CodeTimer (String n ) {
83+ private CodeTimer (String n ) {
7284 name = n ;
7385 enabled = true ;
7486 }
7587
76- public boolean isEnabled ( ) {
77- return enabled ;
88+ public void setThreshold ( long threshold ) {
89+ this . threshold = TimeUnit . NANOSECONDS . convert ( threshold , TimeUnit . MILLISECONDS ) ;
7890 }
7991
80- public void setThreshold (long threshold ) {
81- this .threshold = threshold ;
92+ public void setThreshold (long threshold , TimeUnit timeUnit ) {
93+ this .threshold = TimeUnit .NANOSECONDS .convert (threshold , timeUnit );
94+ }
95+
96+ public void setReportingUnit (TimeUnit reportingUnit ) {
97+ this .reportingUnit = reportingUnit ;
8298 }
8399
84100 public void setEnabled (boolean enabled ) {
@@ -90,62 +106,95 @@ public void increment(String id) {
90106 }
91107
92108 public void increment (String id , int amount ) {
93- counterMap .merge (id , amount , Integer ::sum );
109+ if (!enabled ) {
110+ return ;
111+ }
112+ counterEvents .add (new CounterEvent (amount , id ));
94113 }
95114
96115 public void start (String id , Object ... parameters ) {
97116 if (!enabled ) {
98117 return ;
99118 }
100- if (parameters .length > 0 ) {
101- id = String .format (id , parameters );
102- }
103-
104- Timer timer = timeMap .computeIfAbsent (id , key -> new Timer ());
105- timer .start ();
119+ timerEvents .add (new TimerEvent (TimerEventType .Start , System .nanoTime (), id , parameters ));
106120 }
107121
108122 public void stop (String id , Object ... parameters ) {
109123 if (!enabled ) {
110124 return ;
111125 }
112- if (parameters .length > 0 ) {
113- id = String .format (id , parameters );
114- }
126+ timerEvents .add (new TimerEvent (TimerEventType .Stop , System .nanoTime (), id , parameters ));
127+ }
115128
116- Timer timer = timeMap . get ( id );
117- if (timer == null ) {
118- throw new IllegalArgumentException ( "Could not find timer id: " + id ) ;
129+ private void complete () {
130+ if (! enabled ) {
131+ return ;
119132 }
120- timer .stop ();
121- }
122133
123- public void clear () {
124- timeMap .clear ();
125- counterMap .clear ();
134+ var name = this .name ;
135+ var reportingUnit = this .reportingUnit ;
136+ var threshold = this .threshold ;
137+ var timerEvents = this .timerEvents ;
138+ var counterEvents = this .counterEvents ;
139+ this .timerEvents = new ArrayList <>();
140+ this .counterEvents = new ArrayList <>();
141+
142+ EventQueue .invokeLater (
143+ () -> {
144+ String results = getResults (name , reportingUnit , threshold , timerEvents , counterEvents );
145+ MapTool .getProfilingNoteFrame ().addText (results );
146+ });
126147 }
127148
128- @ Override
129- public String toString () {
130- StringBuilder builder = new StringBuilder (100 );
149+ private static String getResults (
150+ String name ,
151+ TimeUnit reportingUnit ,
152+ long threshold ,
153+ List <TimerEvent > timerEvents ,
154+ List <CounterEvent > counterEvents ) {
155+ final Map <String , Long > counterMap = new LinkedHashMap <>();
156+ // Maps timer names to start times.
157+ final Map <String , Timer > timeMap = new LinkedHashMap <>();
158+
159+ // Count up all the timing events.
160+ for (var event : timerEvents ) {
161+ var id = String .format (event .id (), event .parameters ());
162+ var timer = timeMap .computeIfAbsent (id , Timer ::new );
163+ switch (event .type ()) {
164+ case Start -> timer .startAt (event .timeNs ());
165+ case Stop -> timer .stopAt (event .timeNs ());
166+ }
167+ }
131168
169+ // Count up all the counter events.
170+ for (var event : counterEvents ) {
171+ counterMap .merge (event .id (), event .amount (), Long ::sum );
172+ }
173+
174+ StringBuilder builder = new StringBuilder (100 );
132175 builder
133176 .append ("Timer " )
134177 .append (name )
135178 .append (" (" )
136179 .append (timeMap .size ())
137180 .append (" elements)\n " );
138-
139181 var i = -1 ;
140182 for (var entry : timeMap .entrySet ()) {
141183 ++i ;
142184
143185 var id = entry .getKey ();
144- long elapsed = entry .getValue ().getElapsed () / 1_000_000 ;
186+ long elapsed = entry .getValue ().elapsedNs ;
145187 if (elapsed < threshold ) {
146188 continue ;
147189 }
148- builder .append (String .format (" %3d. %6d ms %s\n " , i , elapsed , id ));
190+
191+ builder .append (
192+ String .format (
193+ " %3d. %6d %s %s\n " ,
194+ i ,
195+ reportingUnit .convert (elapsed , TimeUnit .NANOSECONDS ),
196+ StringUtil .formatTimeUnit (reportingUnit ),
197+ id ));
149198 }
150199
151200 if (!counterMap .isEmpty ()) {
@@ -164,29 +213,29 @@ public String toString() {
164213 return builder .toString ();
165214 }
166215
167- private static class Timer {
168- long elapsed ;
169- long start = -1 ;
216+ private static final class Timer {
217+ String name ;
218+ long elapsedNs = 0 ;
219+ long startTimeNs = -1 ;
170220
171- private long getTime ( ) {
172- return System . nanoTime () ;
221+ public Timer ( String name ) {
222+ this . name = name ;
173223 }
174224
175- public void start () {
176- start = getTime ();
177- }
178-
179- public void stop () {
180- elapsed += (getTime () - start );
181- start = -1 ;
225+ public void startAt (long timeNs ) {
226+ if (startTimeNs >= 0 ) {
227+ log .warn ("Invalid timer state: attempted to start timer {} that was already started" , name );
228+ }
229+ startTimeNs = timeNs ;
182230 }
183231
184- public long getElapsed ( ) {
185- long time = elapsed ;
186- if ( start > 0 ) {
187- time += ( getTime () - start ) ;
232+ public void stopAt ( long timeNs ) {
233+ if ( startTimeNs < 0 ) {
234+ log . warn ( "Invalid timer state: attempted to stop timer {} that was not started" , name );
235+ return ;
188236 }
189- return time ;
237+ elapsedNs += (timeNs - startTimeNs );
238+ startTimeNs = -1 ;
190239 }
191240 }
192241}
0 commit comments