Skip to content

Commit 20f9d00

Browse files
committed
All tests passing. Stubbed out performance metrics tests and added bot frame performance test
1 parent 1970dd5 commit 20f9d00

File tree

4 files changed

+227
-21
lines changed

4 files changed

+227
-21
lines changed

src/main/java/bwapi/BotWrapper.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,18 @@ private Thread createBotThread() {
194194

195195
private void handleEvents() {
196196
ClientData.GameData gameData = game.clientData().gameData();
197-
if (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero) {
198-
performanceMetrics.botResponse.startTiming();
199-
}
200197

201198
// Populate gameOver before invoking event handlers (in case the bot throws)
202199
for (int i = 0; i < gameData.getEventCount(); i++) {
203200
gameOver = gameOver || gameData.getEvents(i).getType() == EventType.MatchEnd;
204201
}
205-
for (int i = 0; i < gameData.getEventCount(); i++) {
206-
EventHandler.operation(eventListener, game, gameData.getEvents(i));
207-
}
208-
performanceMetrics.botResponse.stopTiming();
202+
203+
performanceMetrics.botResponse.timeIf(
204+
! gameOver && (gameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero),
205+
() -> {
206+
for (int i = 0; i < gameData.getEventCount(); i++) {
207+
EventHandler.operation(eventListener, game, gameData.getEvents(i));
208+
}
209+
});
209210
}
210211
}

src/main/java/bwapi/PerformanceMetric.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import java.text.DecimalFormat;
44

5+
/**
6+
* Aggregates labeled time series data.
7+
*/
58
public class PerformanceMetric {
69
private final String name;
710
private final long maxAllowed;
@@ -22,19 +25,45 @@ public class PerformanceMetric {
2225
this.maxAllowed = maxAllowed;
2326
}
2427

28+
/**
29+
* Records the duration of a function call.
30+
* @param runnable The function to time
31+
*/
2532
void time(Runnable runnable) {
2633
startTiming();
2734
runnable.run();
2835
stopTiming();
2936
}
3037

38+
/**
39+
* Convenience method; calls a function; but only records the duration if a condition is met
40+
* @param condition Whether to record the function call duration
41+
* @param runnable The function to call
42+
*/
43+
void timeIf(boolean condition, Runnable runnable) {
44+
if (condition) {
45+
time(runnable);
46+
} else {
47+
runnable.run();
48+
}
49+
}
50+
51+
/**
52+
* Manually start timing.
53+
* The next call to stopTiming() will record the duration in fractional milliseconds.
54+
*/
3155
void startTiming() {
3256
if (timeStarted > 0) {
3357
++interrupted;
3458
}
3559
timeStarted = System.nanoTime();
3660
}
3761

62+
63+
/**
64+
* Manually stop timing.
65+
* If paired with a previous call to startTiming(), records the measured time between the calls in fractional milliseconds.
66+
*/
3867
void stopTiming() {
3968
if (timeStarted <= 0) return;
4069
// Use nanosecond resolution timer, but record in units of milliseconds.
@@ -44,6 +73,9 @@ void stopTiming() {
4473
record(timeDiff / 1000000d);
4574
}
4675

76+
/**
77+
* Manually records a specific value.
78+
*/
4779
void record(double value) {
4880
lastValue = value;
4981
minValue = Math.min(minValue, value);
@@ -54,8 +86,12 @@ void record(double value) {
5486
avgValueExceeding = (avgValueExceeding * samplesExceeding + value) / (samplesExceeding + 1d);
5587
++samplesExceeding;
5688
}
89+
System.out.println(name + " #" + samples + " = " + value); // TODO: REMOVE
5790
}
5891

92+
/**
93+
* @return A pretty-printed description of the recorded values.
94+
*/
5995
@Override
6096
public String toString() {
6197
DecimalFormat formatter = new DecimalFormat("###,###.#");

src/test/java/bwapi/SynchronizationEnvironment.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,20 @@ ClientData.GameData liveGameData() {
6363
return client.clientData().gameData();
6464
}
6565

66+
PerformanceMetrics metrics() {
67+
return bwClient.getPerformanceMetrics();
68+
}
69+
6670
void onFrame(Integer frame, Runnable runnable) {
6771
onFrames.put(frame, runnable);
68-
onEndFrame = Math.max(onEndFrame, frame + 1);
6972
}
7073

7174
void runGame() {
75+
runGame(10);
76+
}
77+
78+
void runGame(int onEndFrame) {
79+
this.onEndFrame = onEndFrame;
7280
if (configuration.async) {
7381
final long MEGABYTE = 1024 * 1024;
7482
long memoryFree = Runtime.getRuntime().freeMemory() / MEGABYTE;

src/test/java/bwapi/SynchronizationTest.java

Lines changed: 174 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
package bwapi;
22

3-
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.junit.Assert.assertEquals;
5-
import static org.junit.Assert.assertNull;
6-
import static org.junit.Assert.assertThrows;
7-
import static org.mockito.Mockito.*;
3+
import org.junit.Test;
84

9-
import java.util.HashMap;
10-
import java.util.Map;
115
import java.util.stream.IntStream;
126

13-
import org.junit.Rule;
14-
import org.junit.Test;
15-
import org.junit.rules.Timeout;
7+
import static org.junit.Assert.assertEquals;
8+
import static org.junit.Assert.assertThrows;
9+
import static org.junit.Assert.assertTrue;
1610

1711
public class SynchronizationTest {
1812

@@ -24,6 +18,26 @@ private void sleepUnchecked(int milliseconds) {
2418
}
2519
}
2620

21+
private String describeApproximateExpectation(double expected, double actual, double margin) {
22+
return "Expected " + expected + " == " + actual + " +/- " + margin;
23+
}
24+
25+
private boolean measureApproximateEquality(double expected, double actual, double margin) {
26+
return expected + margin >= actual && expected - margin <= actual;
27+
}
28+
29+
private void assertWithin(double expected, double actual, double margin) {
30+
assertTrue(
31+
describeApproximateExpectation(expected, actual, margin),
32+
measureApproximateEquality(expected, actual, margin));
33+
}
34+
35+
private void assertWithin(String message, double expected, double actual, double margin) {
36+
assertTrue(
37+
message + ": " + describeApproximateExpectation(expected, actual, margin),
38+
measureApproximateEquality(expected, actual, margin));
39+
}
40+
2741
@Test
2842
public void sync_IfException_ThrowException() throws InterruptedException {
2943
SynchronizationEnvironment environment = new SynchronizationEnvironment();
@@ -48,6 +62,7 @@ public void sync_IfDelay_ThenNoBuffer() throws InterruptedException {
4862
environment.configuration.async = false;
4963
environment.configuration.asyncFrameDurationMs = 1;
5064
environment.configuration.asyncFrameBufferSize = 3;
65+
5166
IntStream.range(0, 5).forEach(frame -> {
5267
environment.onFrame(frame, () -> {
5368
sleepUnchecked(5);
@@ -61,17 +76,163 @@ public void sync_IfDelay_ThenNoBuffer() throws InterruptedException {
6176
}
6277

6378
@Test
64-
public void async_IfDelay_ThenBuffer() throws InterruptedException {
79+
public void async_IfBotDelay_ThenClientBuffers() throws InterruptedException {
6580
SynchronizationEnvironment environment = new SynchronizationEnvironment();
6681
environment.configuration.async = true;
67-
environment.configuration.asyncFrameDurationMs = 1;
82+
environment.configuration.asyncFrameDurationMs = 10;
6883
environment.configuration.asyncFrameBufferSize = 4;
84+
6985
environment.onFrame(1, () -> {
70-
sleepUnchecked(5);
86+
sleepUnchecked(40);
7187
assertEquals("Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount());
7288
assertEquals("Client should be as far ahead as the frame buffer allows", 4, environment.liveGameData().getFrameCount());
7389
assertEquals("Bot should be behind the live game", 3, environment.bwClient.framesBehind());
7490
});
91+
92+
environment.onFrame(6, () -> { // Maybe it should be possible to demand that these assertions pass a frame earlier?
93+
assertEquals("Bot should be observing the live frame", 6, environment.bwClient.getGame().getFrameCount());
94+
assertEquals("Client should not be ahead of the bot", 6, environment.liveGameData().getFrameCount());
95+
assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind());
96+
});
97+
98+
environment.runGame();
99+
}
100+
101+
@Test
102+
public void async_IfBotDelay_ThenClientStalls() throws InterruptedException {
103+
SynchronizationEnvironment environment = new SynchronizationEnvironment();
104+
environment.configuration.async = true;
105+
environment.configuration.asyncFrameDurationMs = 50;
106+
environment.configuration.asyncFrameBufferSize = 5;
107+
108+
environment.onFrame(1, () -> {
109+
sleepUnchecked(125);
110+
assertEquals("3: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount());
111+
assertEquals("3: Client should have progressed as slowly as possible", 3, environment.liveGameData().getFrameCount());
112+
assertEquals("3: Bot should be behind the live game by as little as possible", 2, environment.bwClient.framesBehind());
113+
sleepUnchecked(50);
114+
assertEquals("4: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount());
115+
assertEquals("4: Client should have progressed as slowly as possible", 4, environment.liveGameData().getFrameCount());
116+
assertEquals("4: Bot should be behind the live game by as little as possible", 3, environment.bwClient.framesBehind());
117+
sleepUnchecked(50);
118+
assertEquals("5: Bot should be observing an old frame", 1, environment.bwClient.getGame().getFrameCount());
119+
assertEquals("5: Client should have progressed as slowly as possible", 5, environment.liveGameData().getFrameCount());
120+
assertEquals("5: Bot should be behind the live game by as little as possible", 4, environment.bwClient.framesBehind());
121+
});
122+
75123
environment.runGame();
76124
}
125+
126+
@Test
127+
public void async_IfFrameZeroWaitsEnabled_ThenAllowInfiniteTime() throws InterruptedException {
128+
SynchronizationEnvironment environment = new SynchronizationEnvironment();
129+
environment.configuration.async = true;
130+
environment.configuration.unlimitedFrameZero = true;
131+
environment.configuration.asyncFrameDurationMs = 5;
132+
environment.configuration.asyncFrameBufferSize = 2;
133+
134+
environment.onFrame(0, () -> {
135+
sleepUnchecked(50);
136+
assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount());
137+
assertEquals("Client should still be on frame zero", 0, environment.liveGameData().getFrameCount());
138+
assertEquals("Bot should not be behind the live game", 0, environment.bwClient.framesBehind());
139+
});
140+
141+
environment.runGame();
142+
}
143+
144+
@Test
145+
public void async_IfFrameZeroWaitsDisabled_ThenClientBuffers() throws InterruptedException {
146+
SynchronizationEnvironment environment = new SynchronizationEnvironment();
147+
environment.configuration.async = true;
148+
environment.configuration.unlimitedFrameZero = false;
149+
environment.configuration.asyncFrameDurationMs = 5;
150+
environment.configuration.asyncFrameBufferSize = 2;
151+
152+
environment.onFrame(0, () -> {
153+
sleepUnchecked(50);
154+
assertEquals("Bot should still be on frame zero", 0, environment.bwClient.getGame().getFrameCount());
155+
assertEquals("Client should have advanced to the next frame", 2, environment.liveGameData().getFrameCount());
156+
assertEquals("Bot should be behind the live game", 2, environment.bwClient.framesBehind());
157+
});
158+
159+
environment.runGame();
160+
}
161+
162+
@Test
163+
public void async_MeasurePerformance_TotalFrameDuration() {
164+
165+
}
166+
167+
@Test
168+
public void async_MeasurePerformance_CopyingToBuffer() {
169+
170+
}
171+
172+
@Test
173+
public void async_MeasurePerformance_IntentionallyBlocking() {
174+
175+
}
176+
177+
@Test
178+
public void async_MeasurePerformance_FrameBufferSize() {
179+
180+
}
181+
182+
@Test
183+
public void async_MeasurePerformance_FlushSideEffects() {
184+
185+
}
186+
187+
/**
188+
* Number of milliseconds of leeway to give in performance metrics.
189+
* Increase if tests are flaky due to variance in execution speed.
190+
*/
191+
private final static long MS_MARGIN = 10;
192+
193+
@Test
194+
public void async_MeasurePerformance_BotResponse() {
195+
SynchronizationEnvironment environment = new SynchronizationEnvironment();
196+
197+
// Frame zero appears to take an extra 60ms, so let's disable timing for it
198+
// (and also verify that we omit frame zero from performance metrics)
199+
environment.configuration.unlimitedFrameZero = true;
200+
201+
environment.onFrame(1, () -> {
202+
sleepUnchecked(100);
203+
});
204+
environment.onFrame(2, () -> {
205+
assertWithin("2: Bot response average", 100, environment.metrics().botResponse.avgValue, MS_MARGIN);
206+
assertWithin("2: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN);
207+
assertWithin("2: Bot response maximum", 100, environment.metrics().botResponse.maxValue, MS_MARGIN);
208+
assertWithin("2: Bot response previous", 100, environment.metrics().botResponse.lastValue, MS_MARGIN);
209+
sleepUnchecked(300);
210+
});
211+
environment.onFrame(3, () -> {
212+
assertWithin("3: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN);
213+
assertWithin("3: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN);
214+
assertWithin("3: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN);
215+
assertWithin("3: Bot response previous", 300, environment.metrics().botResponse.lastValue, MS_MARGIN);
216+
sleepUnchecked(200);
217+
});
218+
219+
environment.runGame(4);
220+
221+
assertWithin("Final: Bot response average", 200, environment.metrics().botResponse.avgValue, MS_MARGIN);
222+
assertWithin("Final: Bot response minimum", 100, environment.metrics().botResponse.minValue, MS_MARGIN);
223+
assertWithin("Final: Bot response maximum", 300, environment.metrics().botResponse.maxValue, MS_MARGIN);
224+
assertWithin("Final: Bot response previous", 200, environment.metrics().botResponse.lastValue, MS_MARGIN);
225+
}
226+
227+
@Test
228+
public void async_MeasurePerformance_BwapiResponse() {
229+
230+
}
231+
232+
@Test
233+
public void async_MeasurePerformance_BotIdle() {
234+
235+
}
236+
237+
77238
}

0 commit comments

Comments
 (0)