Skip to content

Commit 4b27adc

Browse files
committed
Reduce overhead of multiplayer logging
The current logging operations use `writeText` and `appendText` helpers, which create a `FileOutputStream` underneath for every log. This replaces the helpers with a `BufferedWriter` and continuous background flushing.
1 parent 5f0444b commit 4b27adc

File tree

3 files changed

+139
-22
lines changed

3 files changed

+139
-22
lines changed

src/com/osudroid/multiplayer/Multiplayer.kt

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.osudroid.multiplayer
22

33
import android.net.*
4-
import android.text.format.DateFormat
54
import android.util.Log
65
import com.osudroid.multiplayer.api.RoomAPI
76
import com.osudroid.multiplayer.api.data.Room
@@ -14,14 +13,12 @@ import kotlinx.coroutines.CoroutineScope
1413
import kotlinx.coroutines.Dispatchers
1514
import kotlinx.coroutines.launch
1615
import org.json.JSONArray
17-
import ru.nsu.ccfit.zuev.osu.Config
1816
import ru.nsu.ccfit.zuev.osu.GlobalManager
1917
import ru.nsu.ccfit.zuev.osu.MainActivity
2018
import ru.nsu.ccfit.zuev.osu.ToastLogger
2119
import ru.nsu.ccfit.zuev.osu.menu.*
2220
import ru.nsu.ccfit.zuev.osu.online.OnlineManager
2321
import ru.nsu.ccfit.zuev.osu.scoring.StatisticV2
24-
import java.io.File
2522

2623
object Multiplayer {
2724

@@ -79,15 +76,7 @@ object Multiplayer {
7976
val isConnected
8077
get() = room != null
8178

82-
83-
private val LOG_FILE = File("${Config.getDefaultCorePath()}/Log", "multi_log.txt").apply {
84-
85-
parentFile?.mkdirs()
86-
87-
if (!exists()) {
88-
createNewFile()
89-
}
90-
}
79+
private val logger = MultiplayerLogger()
9180

9281
private val reconnectionScope by lazy { CoroutineScope(Dispatchers.Default) }
9382

@@ -102,7 +91,7 @@ object Multiplayer {
10291

10392

10493
init {
105-
LOG_FILE.writeText("[${"yyyy/MM/dd hh:mm:ss".fromDate()}] Client ${MainActivity.versionName} started.")
94+
logger.log("[${"yyyy/MM/dd hh:mm:ss".fromDate()}] Client ${MainActivity.versionName} started.")
10695
}
10796

10897

@@ -295,19 +284,16 @@ object Multiplayer {
295284

296285
@JvmStatic
297286
fun log(text: String) {
298-
val timestamp = DateFormat.format("hh:mm:ss", System.currentTimeMillis())
299-
300-
LOG_FILE.appendText("\n[$timestamp] $text")
301-
Log.i("Multiplayer", text)
287+
logger.log(text)
302288
}
303289

304290
@JvmStatic
305291
fun log(e: Throwable) {
306-
val time = "hh:mm:ss".formatTimeMilliseconds(System.currentTimeMillis())
307-
val stacktrace = Log.getStackTraceString(e)
308-
309-
LOG_FILE.appendText("\n[$time] EXCEPTION: ${e.javaClass.simpleName}\n$stacktrace")
292+
logger.log(e)
293+
}
310294

311-
Log.e("Multiplayer", "An exception has been thrown.", e)
295+
@JvmStatic
296+
fun flushLog() {
297+
logger.flush()
312298
}
313299
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.osudroid.multiplayer
2+
3+
import android.util.Log
4+
import com.reco1l.toolkt.kotlin.formatTimeMilliseconds
5+
import java.io.File
6+
import kotlinx.coroutines.CoroutineScope
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.SupervisorJob
9+
import kotlinx.coroutines.launch
10+
import ru.nsu.ccfit.zuev.osu.Config
11+
12+
/**
13+
* Logger for multiplayer events.
14+
*/
15+
class MultiplayerLogger : AutoCloseable {
16+
private val dispatcher = Dispatchers.IO.limitedParallelism(1)
17+
private val scope = CoroutineScope(dispatcher + SupervisorJob())
18+
19+
@Volatile
20+
private var isClosed = false
21+
22+
private val writer = File("${Config.getDefaultCorePath()}/Log", "multi_log.txt").apply {
23+
parentFile?.mkdirs()
24+
25+
if (!exists()) {
26+
createNewFile()
27+
}
28+
}.bufferedWriter()
29+
30+
/**
31+
* Logs a message to the log file.
32+
*
33+
* @param text The message to log.
34+
*/
35+
fun log(text: String) {
36+
if (isClosed) {
37+
return
38+
}
39+
40+
val timestamp = "hh:mm:ss".formatTimeMilliseconds(System.currentTimeMillis())
41+
42+
Log.i("Multiplayer", text)
43+
write("\n[$timestamp] $text")
44+
}
45+
46+
/**
47+
* Logs a [Throwable] to the log file.
48+
*
49+
* @param e The [Throwable] to log.
50+
*/
51+
fun log(e: Throwable) {
52+
if (isClosed) {
53+
return
54+
}
55+
56+
val time = "hh:mm:ss".formatTimeMilliseconds(System.currentTimeMillis())
57+
val stacktrace = Log.getStackTraceString(e)
58+
59+
Log.e("Multiplayer", "An exception has been thrown.", e)
60+
write("\n[$time] EXCEPTION: ${e.javaClass.simpleName}\n$stacktrace")
61+
}
62+
63+
/**
64+
* Flushes the underlying buffer.
65+
*/
66+
fun flush() {
67+
if (isClosed) {
68+
return
69+
}
70+
71+
scope.launch {
72+
try {
73+
writer.flush()
74+
} catch (e: Exception) {
75+
e.printStackTrace()
76+
}
77+
}
78+
}
79+
80+
private fun write(str: String) {
81+
if (isClosed) {
82+
return
83+
}
84+
85+
scope.launch {
86+
try {
87+
writer.write(str)
88+
} catch (ex: Exception) {
89+
ex.printStackTrace()
90+
}
91+
}
92+
}
93+
94+
override fun close() {
95+
if (isClosed) {
96+
return
97+
}
98+
99+
isClosed = true
100+
101+
scope.launch {
102+
try {
103+
writer.flush()
104+
writer.close()
105+
} catch (e: Exception) {
106+
e.printStackTrace()
107+
}
108+
}
109+
}
110+
}

src/ru/nsu/ccfit/zuev/osu/MainActivity.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import java.util.HashSet;
7979
import java.util.concurrent.Executors;
8080
import java.util.concurrent.ScheduledExecutorService;
81+
import java.util.concurrent.ScheduledFuture;
8182
import java.util.concurrent.TimeUnit;
8283

8384
import ru.nsu.ccfit.zuev.audio.serviceAudio.SaveServiceObject;
@@ -103,6 +104,7 @@ public class MainActivity extends BaseGameActivity implements
103104
private boolean willReplay = false;
104105
private static boolean activityVisible = true;
105106
private static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
107+
private ScheduledFuture<?> logFlushFuture;
106108
private Display display;
107109
private DisplayManager.DisplayListener displayListener;
108110
private float currentRefreshRate = 60;
@@ -345,6 +347,8 @@ public void onLoadComplete() {
345347
AccessibilityDetector.check(MainActivity.this);
346348
}, 0, 100, TimeUnit.MILLISECONDS);
347349

350+
logFlushFuture = scheduledExecutor.scheduleAtFixedRate(Multiplayer::flushLog, 0, 5, TimeUnit.SECONDS);
351+
348352
if (roomInviteLink != null) {
349353
Multiplayer.connectFromLink(roomInviteLink);
350354
} else if (willReplay) {
@@ -625,6 +629,8 @@ public void onResume() {
625629
super.onResume();
626630
activityVisible = true;
627631

632+
logFlushFuture = scheduledExecutor.scheduleAtFixedRate(Multiplayer::flushLog, 0, 5, TimeUnit.SECONDS);
633+
628634
if (mEngine == null) {
629635
return;
630636
}
@@ -641,6 +647,12 @@ public void onPause() {
641647
super.onPause();
642648
activityVisible = false;
643649

650+
if (logFlushFuture != null && !logFlushFuture.isCancelled()) {
651+
logFlushFuture.cancel(false);
652+
}
653+
654+
Multiplayer.flushLog();
655+
644656
if (mEngine == null) {
645657
return;
646658
}
@@ -665,12 +677,21 @@ public void onPause() {
665677
@Override
666678
public void onStop() {
667679
super.onStop();
680+
681+
if (logFlushFuture != null && !logFlushFuture.isCancelled()) {
682+
logFlushFuture.cancel(false);
683+
}
684+
685+
Multiplayer.flushLog();
686+
668687
activityVisible = false;
669688
}
670689

671690
@Override
672691
protected void onDestroy() {
673692
super.onDestroy();
693+
694+
Multiplayer.flushLog();
674695
((DisplayManager) getSystemService(DISPLAY_SERVICE)).unregisterDisplayListener(displayListener);
675696
}
676697

0 commit comments

Comments
 (0)