Skip to content

Commit ab695ad

Browse files
committed
Assembled complete suite of frame-duration and BWAPI communication performance metrics
1 parent a333f15 commit ab695ad

File tree

4 files changed

+111
-85
lines changed

4 files changed

+111
-85
lines changed

src/main/java/bwapi/BWClient.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ public BWClientConfiguration getConfiguration() {
4343
return configuration;
4444
}
4545

46+
/**
47+
* @return Whether the current frame should be subject to timing.
48+
*/
49+
boolean doTime() {
50+
return ! configuration.unlimitedFrameZero || (client.isConnected() && client.clientData().gameData().getFrameCount() > 0);
51+
}
52+
4653
/**
4754
* @return The number of frames between the one exposed to the bot and the most recent received by JBWAPI.
4855
* This tracks the size of the frame buffer except when the game is paused (which results in multiple frames arriving with the same count).
@@ -114,32 +121,26 @@ public void startGame(BWClientConfiguration gameConfiguration) {
114121
}
115122
}
116123
while (liveGameData.isInGame()) {
117-
boolean timeFrame = liveGameData.getFrameCount() > 0 || ! configuration.unlimitedFrameZero;
118-
119124
long ticksBefore = Kernel32.INSTANCE.GetTickCount();
120-
performanceMetrics.endToEndFrameDuration.startTiming();
121-
performanceMetrics.jbwapiFrameDuration.timeIf(
122-
timeFrame,
123-
() -> {
124-
botWrapper.onFrame();
125-
performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData));
126-
});
125+
126+
botWrapper.onFrame();
127+
performanceMetrics.flushSideEffects.time(() -> getGame().sideEffects.flushTo(liveGameData));
128+
performanceMetrics.frameDurationReceiveToSend.stopTiming();
127129
long ticksAfter = Kernel32.INSTANCE.GetTickCount();
128-
if (timeFrame) {
130+
131+
// Measure differential between JVM timer and WinAPI's GetTickCount, used by BWAPI 4.4 and below
132+
if (doTime()) {
129133
long deltaTicks = ticksAfter - ticksBefore;
130-
long deltaMillis = (long) performanceMetrics.jbwapiFrameDuration.runningTotal.last;
134+
long deltaMillis = (long) performanceMetrics.frameDurationReceiveToSend.runningTotal.last;
131135
long delta = deltaMillis - deltaTicks;
132-
if (Math.abs(delta) > 1000) {
133-
System.out.println("Got weird tick delta: " + ticksAfter + ", " + ticksBefore + ", " + deltaTicks + ", " + deltaMillis + ", " + delta);
134-
performanceMetrics.weirdTimeDelta.record(1);
135-
} else if (delta > 0) {
136+
if (delta > 0) {
136137
performanceMetrics.positiveTimeDelta.record(delta);
137138
} else if (delta < 0) {
138139
performanceMetrics.negativeTimeDelta.record(-delta);
139140
}
140141
}
141142

142-
performanceMetrics.bwapiResponse.time(client::update);
143+
client.update();
143144
if (!client.isConnected()) {
144145
System.out.println("Reconnecting...");
145146
client.reconnect();

src/main/java/bwapi/Client.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,13 @@ boolean connect() {
231231
}
232232

233233
void update() {
234-
// Indicate that we are done with the current frame
235-
byte code = 1;
234+
235+
// Tell BWAPI that we are done with the current frame
236+
bwClient.getPerformanceMetrics().frameDurationReceiveToSend.stopTiming();
237+
bwClient.getPerformanceMetrics().communicationSendToReceive.startTiming();
238+
bwClient.getPerformanceMetrics().communicationSendToSent.startTiming();
236239
try {
237-
pipeObjectHandle.writeByte(code);
238-
bwClient.getPerformanceMetrics().endToEndFrameDuration.stopTiming();
240+
pipeObjectHandle.writeByte(1); // The "frame done" signal
239241
}
240242
catch (Exception e) {
241243
System.err.println("failed, disconnecting");
@@ -245,27 +247,33 @@ void update() {
245247
disconnect();
246248
return;
247249
}
248-
// Wait for BWAPI to indicate that a new frame is ready
249-
while (code != 2) {
250+
bwClient.getPerformanceMetrics().communicationSendToSent.stopTiming();
251+
bwClient.getPerformanceMetrics().frameDurationReceiveToSent.stopTiming();
252+
253+
// Listen for BWAPI to indicate that a new frame is ready
254+
bwClient.getPerformanceMetrics().communicationListenToReceive.startTiming();
255+
byte code = 1;
256+
while (code != 2) { // The "frame ready" signal
250257
try {
251258
code = pipeObjectHandle.readByte();
252-
/*
253-
if (code != 2) {
254-
try {
255-
Thread.sleep(0, 1);
256-
} catch (InterruptedException ignored) {}
257-
}
258-
*/
259-
}
260-
catch (Exception e) {
259+
} catch (Exception e) {
261260
System.err.println("failed, disconnecting");
262261
if (bwClient.getConfiguration().debugConnection) {
263262
e.printStackTrace();
264263
}
265264
disconnect();
266-
return;
265+
break;
267266
}
268267
}
268+
bwClient.getPerformanceMetrics().communicationListenToReceive.stopTiming();
269+
bwClient.getPerformanceMetrics().communicationSendToReceive.stopTiming();
270+
271+
if (bwClient.doTime()) {
272+
bwClient.getPerformanceMetrics().frameDurationReceiveToSend.startTiming();
273+
bwClient.getPerformanceMetrics().frameDurationReceiveToSent.startTiming();
274+
bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.stopTiming();
275+
bwClient.getPerformanceMetrics().frameDurationReceiveToReceive.startTiming();
276+
}
269277
}
270278

271279
private void sleep(final int millis) {

src/main/java/bwapi/PerformanceMetric.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,18 @@ public String toString() {
4242
}
4343

4444
private final String name;
45-
public int interrupted = 0;
4645
private long timeStarted = 0;
46+
public int interrupted = 0;
4747

4848
RunningTotal runningTotal = new RunningTotal();
4949
private ArrayList<Threshold> thresholds = new ArrayList<>();
5050

51-
PerformanceMetric(String name, double... thresholds) {
51+
PerformanceMetric(PerformanceMetrics metrics, String name, double... thresholds) {
5252
this.name = name;
5353
for (double threshold : thresholds) {
5454
this.thresholds.add(new Threshold(threshold));
5555
}
56+
metrics.addMetric(this);
5657
}
5758

5859
/**
@@ -121,7 +122,7 @@ public String toString() {
121122
}
122123
DecimalFormat formatter = new DecimalFormat("###,###.#");
123124
String output = name
124-
+ ": "
125+
+ ":\n"
125126
+ formatter.format(runningTotal.samples)
126127
+ " samples averaging "
127128
+ formatter.format(runningTotal.mean)
Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
package bwapi;
22

3+
import java.util.ArrayList;
4+
35
/**
46
* Collects various performance metrics.
57
*/
68
public class PerformanceMetrics {
79

810
/**
9-
* Total duration of the frame from JBWAPI's perspective,
10-
* exclusive of time modifying shared memory to indicate frame completion.
11-
* Likely to be at least a little bit of an undercount from the perspective of BWAPI,
12-
* given that the tournament module is timing a superset of JBWAPI's execution time.
11+
* Duration of the frame cycle steps measured by BWAPI,
12+
* from receiving a frame to BWAPI
13+
* to sending commands back
14+
* *exclusive* of the time spent sending commands back.
15+
*/
16+
PerformanceMetric frameDurationReceiveToSend;
17+
18+
/**
19+
* Duration of the frame cycle steps measured by BWAPI,
20+
* from receiving a frame to BWAPI
21+
* to sending commands back
22+
* *inclusive* of the time spent sending commands back.
1323
*/
14-
PerformanceMetric jbwapiFrameDuration;
24+
PerformanceMetric frameDurationReceiveToSent;
1525

1626
/**
17-
* Total duration of the frame from JBWAPI's perspective,
18-
* inclusive of time modifying shared memory to indicate frame completion.
19-
* Likely to be at least a little bit of an undercount from the perspective of BWAPI,
20-
* given that the tournament module is timing a superset of JBWAPI's execution time.
27+
* Duration of a frame cycle originating at
28+
* the time when JBWAPI observes a new frame in shared memory.
2129
*/
22-
PerformanceMetric endToEndFrameDuration;
30+
PerformanceMetric frameDurationReceiveToReceive;
2331

2432
/**
2533
* Time spent copying game data from system pipe shared memory to a frame buffer.
@@ -56,9 +64,23 @@ public class PerformanceMetrics {
5664
PerformanceMetric botResponse;
5765

5866
/**
59-
* Time spent waiting for a response from BWAPI; is likely reflective of the performance of any opponent bots.
67+
* Time spent waiting for a response from BWAPI,
68+
* inclusive of the time spent sending the signal to BWAPI
69+
* and the time spent waiting for and receiving it.
6070
*/
61-
PerformanceMetric bwapiResponse;
71+
PerformanceMetric communicationSendToReceive;
72+
73+
/**
74+
* Time spent sending the "frame complete" signal to BWAPI.
75+
* Significant durations would indicate something blocking writes to shared memory.
76+
*/
77+
PerformanceMetric communicationSendToSent;
78+
79+
/**
80+
* Time spent waiting for a "frame ready" signal from BWAPI.
81+
* This time likely additional response time spent by other bots and StarCraft itself.
82+
*/
83+
PerformanceMetric communicationListenToReceive;
6284

6385
/**
6486
* Time bot spends idle.
@@ -79,62 +101,56 @@ public class PerformanceMetrics {
79101
PerformanceMetric excessSleep;
80102

81103
/**
82-
* Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount.
104+
* Instances of System.nanoTime() measuring a longer frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4.
83105
*/
84106
PerformanceMetric positiveTimeDelta;
85107

86108
/**
87-
* Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount.
109+
* Instances of System.nanoTime() measuring a shorter frame duration with respect to WinAPI's GetTickCount which BWAPI uses up to 4.4.
88110
*/
89111
PerformanceMetric negativeTimeDelta;
90112

91-
/**
92-
* When Kernel32.INSTANCE.GetTickCount() returns at least one inexplicable value
93-
*/
94-
PerformanceMetric weirdTimeDelta;
95-
96113
private BWClientConfiguration configuration;
114+
private ArrayList<PerformanceMetric> performanceMetrics = new ArrayList<>();
97115

98116
PerformanceMetrics(BWClientConfiguration configuration) {
99117
this.configuration = configuration;
100118
reset();
101119
}
102120

103121
public void reset() {
104-
jbwapiFrameDuration = new PerformanceMetric("JBWAPI frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
105-
endToEndFrameDuration = new PerformanceMetric("End-to-end frame duration", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
106-
copyingToBuffer = new PerformanceMetric("Time copying to buffer", 5, 10, 15, 20, 25, 30);
107-
intentionallyBlocking = new PerformanceMetric("Blocking with full buffer", 0);
108-
frameBufferSize = new PerformanceMetric("Frames buffered", 0, 1);
109-
framesBehind = new PerformanceMetric("Frames behind real-time", 0, 1);
110-
flushSideEffects = new PerformanceMetric("Flushing side effects", 1, 3, 5);
111-
botResponse = new PerformanceMetric("Bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
112-
bwapiResponse = new PerformanceMetric("Responses from BWAPI", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
113-
botIdle = new PerformanceMetric("Bot idle", Long.MAX_VALUE);
114-
clientIdle = new PerformanceMetric("Client idling", configuration.maxFrameDurationMs);
115-
excessSleep = new PerformanceMetric("Excess sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
116-
positiveTimeDelta = new PerformanceMetric("Positive timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
117-
negativeTimeDelta = new PerformanceMetric("Negative timer delta", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
118-
weirdTimeDelta = new PerformanceMetric("Weird timer delta");
122+
performanceMetrics.clear();
123+
frameDurationReceiveToSend = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> before sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
124+
frameDurationReceiveToSent = new PerformanceMetric(this, "Frame duration: Receiving 'frame ready' -> after sending 'frame done'", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
125+
frameDurationReceiveToReceive = new PerformanceMetric(this, "Frame duration: From BWAPI receive to BWAPI receive", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
126+
communicationSendToReceive = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30);
127+
communicationSendToSent = new PerformanceMetric(this, "BWAPI duration: Before sending 'frame done' -> After sending 'frame done'", 1, 3, 5, 10, 15, 20, 30);
128+
communicationListenToReceive = new PerformanceMetric(this, "BWAPI duration: Before listening for 'frame ready' -> After receiving 'frame ready'", 1, 3, 5, 10, 15, 20, 30);
129+
copyingToBuffer = new PerformanceMetric(this, "Copying frame to buffer", 5, 10, 15, 20, 25, 30);
130+
intentionallyBlocking = new PerformanceMetric(this, "Time holding frame until buffer frees capacity", 0);
131+
frameBufferSize = new PerformanceMetric(this, "Frames already buffered when enqueuing a new frame", 0, 1);
132+
framesBehind = new PerformanceMetric(this, "Frames behind real-time when handling events", 0, 1);
133+
flushSideEffects = new PerformanceMetric(this, "Time flushing side effects", 1, 3, 5);
134+
botResponse = new PerformanceMetric(this, "Duration of bot event handlers", 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
135+
botIdle = new PerformanceMetric(this, "Time bot spent idle", Long.MAX_VALUE);
136+
clientIdle = new PerformanceMetric(this, "Time client spent waiting for bot", configuration.maxFrameDurationMs);
137+
excessSleep = new PerformanceMetric(this, "Excess duration of client sleep", 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
138+
positiveTimeDelta = new PerformanceMetric(this, "Positive timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
139+
negativeTimeDelta = new PerformanceMetric(this, "Negative timer discrepancy compared to BWAPI", 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 85);
140+
}
141+
142+
void addMetric(PerformanceMetric performanceMetric) {
143+
performanceMetrics.add(performanceMetric);
119144
}
120145

121146
@Override
122147
public String toString() {
123-
return "Performance metrics:"
124-
+ "\n" + jbwapiFrameDuration.toString()
125-
+ "\n" + endToEndFrameDuration.toString()
126-
+ "\n" + copyingToBuffer.toString()
127-
+ "\n" + intentionallyBlocking.toString()
128-
+ "\n" + frameBufferSize.toString()
129-
+ "\n" + framesBehind.toString()
130-
+ "\n" + flushSideEffects.toString()
131-
+ "\n" + botResponse.toString()
132-
+ "\n" + bwapiResponse.toString()
133-
+ "\n" + botIdle.toString()
134-
+ "\n" + clientIdle.toString()
135-
+ "\n" + excessSleep.toString()
136-
+ "\n" + positiveTimeDelta.toString()
137-
+ "\n" + negativeTimeDelta.toString()
138-
+ "\n" + weirdTimeDelta.toString();
148+
StringBuilder outputBuilder = new StringBuilder();
149+
outputBuilder.append("Performance metrics:");
150+
performanceMetrics.forEach(metric -> {
151+
outputBuilder.append("\n");
152+
outputBuilder.append(metric.toString());
153+
});
154+
return outputBuilder.toString();
139155
}
140156
}

0 commit comments

Comments
 (0)