Skip to content

Commit 42cbb6d

Browse files
committed
Switch to streaming events for timing rather than in-line map lookups
Report timer results off thread
1 parent e8d7fa4 commit 42cbb6d

2 files changed

Lines changed: 83 additions & 51 deletions

File tree

src/main/java/net/rptools/lib/CodeTimer.java

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@
1414
*/
1515
package net.rptools.lib;
1616

17+
import java.awt.EventQueue;
1718
import java.util.ArrayList;
1819
import java.util.LinkedHashMap;
1920
import java.util.List;
2021
import java.util.Map;
2122
import java.util.concurrent.TimeUnit;
2223
import net.rptools.maptool.client.AppState;
2324
import net.rptools.maptool.client.MapTool;
25+
import org.apache.logging.log4j.LogManager;
26+
import org.apache.logging.log4j.Logger;
2427

2528
public class CodeTimer {
2629
private static final ThreadLocal<CodeTimer> ROOT_TIMER =
2730
ThreadLocal.withInitial(() -> new CodeTimer(""));
2831
private static final ThreadLocal<List<CodeTimer>> timerStack =
2932
ThreadLocal.withInitial(ArrayList::new);
33+
private static final Logger log = LogManager.getLogger(CodeTimer.class);
3034

3135
@FunctionalInterface
3236
public interface TimedSection<Ex extends Throwable> {
@@ -50,11 +54,7 @@ public static <Ex extends Exception> void using(String name, TimedSection<Ex> ca
5054
final var lastTimer = stack.removeLast();
5155
assert lastTimer == timer : "Timer stack is corrupted";
5256

53-
if (timer.isEnabled()) {
54-
String results = timer.toString();
55-
MapTool.getProfilingNoteFrame().addText(results);
56-
}
57-
timer.clear();
57+
timer.complete();
5858
}
5959
}
6060

@@ -63,8 +63,18 @@ public static CodeTimer get() {
6363
return stack.isEmpty() ? ROOT_TIMER.get() : stack.getLast();
6464
}
6565

66-
private final Map<String, Integer> counterMap = new LinkedHashMap<>();
67-
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+
6878
private final String name;
6979
private long threshold = 1;
7080
private TimeUnit reportingUnit = TimeUnit.MILLISECONDS;
@@ -75,10 +85,6 @@ private CodeTimer(String n) {
7585
enabled = true;
7686
}
7787

78-
public boolean isEnabled() {
79-
return enabled;
80-
}
81-
8288
public void setThreshold(long threshold) {
8389
this.threshold = TimeUnit.NANOSECONDS.convert(threshold, TimeUnit.MILLISECONDS);
8490
}
@@ -103,58 +109,81 @@ public void increment(String id, int amount) {
103109
if (!enabled) {
104110
return;
105111
}
106-
counterMap.merge(id, amount, Integer::sum);
112+
counterEvents.add(new CounterEvent(amount, id));
107113
}
108114

109115
public void start(String id, Object... parameters) {
110116
if (!enabled) {
111117
return;
112118
}
113-
if (parameters.length > 0) {
114-
id = String.format(id, parameters);
115-
}
116-
117-
Timer timer = timeMap.computeIfAbsent(id, key -> new Timer());
118-
timer.start();
119+
timerEvents.add(new TimerEvent(TimerEventType.Start, System.nanoTime(), id, parameters));
119120
}
120121

121122
public void stop(String id, Object... parameters) {
122123
if (!enabled) {
123124
return;
124125
}
125-
if (parameters.length > 0) {
126-
id = String.format(id, parameters);
126+
timerEvents.add(new TimerEvent(TimerEventType.Stop, System.nanoTime(), id, parameters));
127+
}
128+
129+
private void complete() {
130+
if (!enabled) {
131+
return;
127132
}
128133

129-
Timer timer = timeMap.get(id);
130-
if (timer == null) {
131-
throw new IllegalArgumentException("Could not find timer id: " + id);
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+
});
147+
}
148+
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+
}
132167
}
133-
timer.stop();
134-
}
135168

136-
public void clear() {
137-
timeMap.clear();
138-
counterMap.clear();
139-
}
169+
// Count up all the counter events.
170+
for (var event : counterEvents) {
171+
counterMap.merge(event.id(), event.amount(), Long::sum);
172+
}
140173

141-
@Override
142-
public String toString() {
143174
StringBuilder builder = new StringBuilder(100);
144-
145175
builder
146176
.append("Timer ")
147177
.append(name)
148178
.append(" (")
149179
.append(timeMap.size())
150180
.append(" elements)\n");
151-
152181
var i = -1;
153182
for (var entry : timeMap.entrySet()) {
154183
++i;
155184

156185
var id = entry.getKey();
157-
long elapsed = entry.getValue().getElapsed();
186+
long elapsed = entry.getValue().elapsedNs;
158187
if (elapsed < threshold) {
159188
continue;
160189
}
@@ -184,29 +213,29 @@ public String toString() {
184213
return builder.toString();
185214
}
186215

187-
private static class Timer {
188-
long elapsed;
189-
long start = -1;
216+
private static final class Timer {
217+
String name;
218+
long elapsedNs = 0;
219+
long startTimeNs = -1;
190220

191-
private long getTime() {
192-
return System.nanoTime();
221+
public Timer(String name) {
222+
this.name = name;
193223
}
194224

195-
public void start() {
196-
start = getTime();
197-
}
198-
199-
public void stop() {
200-
elapsed += (getTime() - start);
201-
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;
202230
}
203231

204-
public long getElapsed() {
205-
long time = elapsed;
206-
if (start > 0) {
207-
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;
208236
}
209-
return time;
237+
elapsedNs += (timeNs - startTimeNs);
238+
startTimeNs = -1;
210239
}
211240
}
212241
}

src/main/java/net/rptools/maptool/client/ui/zone/gdx/GdxRenderer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.text.NumberFormat;
4040
import java.util.*;
4141
import java.util.List;
42+
import java.util.concurrent.TimeUnit;
4243
import java.util.zip.Deflater;
4344
import javax.swing.*;
4445
import net.rptools.lib.AwtUtil;
@@ -371,6 +372,8 @@ public void render() {
371372
"GdxRenderer.renderZone",
372373
timer -> {
373374
timer.setThreshold(10);
375+
timer.setThreshold(1, TimeUnit.MICROSECONDS);
376+
timer.setReportingUnit(TimeUnit.MICROSECONDS);
374377
doRendering();
375378
});
376379
} catch (Exception ex) {

0 commit comments

Comments
 (0)