Created
March 23, 2023 12:41
-
-
Save jmbjorndalen/23e6922b8ef069014463ffc97d717506 to your computer and use it in GitHub Desktop.
Hack to fix high idle CPU time for Minecraft servers on a Linux host
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
This is a quick hack that is intended to be used as a tool to help diagnose | |
high CPU usage by what should be an idle minecraft server. | |
Use with care! It may break things. | |
Needs to run as root to work. | |
The main problem is that Minecraft appears to be running parkNanos in a tight | |
loop with a very low timeout. Linux has higher precision timeouts by default | |
than, for instance, Windows. When this happens, you can observe that Minecraft | |
spins calling 3 system calls | |
- clock_gettime | |
- futex - with a timeout 1 microsecond away from the timestamp returned by clock_gettime | |
- futex - possibly to wake some other thread after the first futex times out. | |
The result is that an idle Minecraft server ends up consuming considerable CPU | |
time. As far as I could tell, out of around 60-70 threads on my modded server, | |
there are at least 3 threads that consume significant time. The most active | |
will be around 35% CPU time (out of a single core) on my system. | |
This hack works by tweaking the timer resolution for Minecraft's Java threads, | |
setting them closer to what a Windows developer might have experienced. | |
The problem can be a bit confusing to diagnose. If you, for instance, use the | |
spark profiler in Minecraft, it looks like the process is spending 99% of its | |
time sleeping. As far as I can tell, this is an illusion caused by the level | |
that the profiler works on. | |
Disclaimer: | |
- I have not tried to look at how Minecraft uses parkNanos internally. | |
- I have not traced the JDK source (apparently, it uses pthread condition | |
variables that then use futexes, but I have not verified this). | |
References (for more details on the problem and the hack to alleviate it): | |
- https://bugs.mojang.com/browse/MC-183518 | |
- https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking/ | |
TODO: | |
- probably don't need to do it for all of the threads (possibly only | |
those that call parkNanos). This is simpler though. | |
""" | |
import sys | |
import re | |
import subprocess | |
def check_cmd_java(tpid): | |
"Check that the command line for the pid starts with java" | |
cmd = open(f"/proc/{tpid}/cmdline").read().strip() | |
return cmd.startswith("java") | |
def get_java_pids(mainpid): | |
"""Returns a list of pids for the main process (the first in the list) and the threads in the java process""" | |
p = subprocess.Popen(f"jstack {pid}", shell=True, stdout=subprocess.PIPE) | |
jpids = [mainpid] | |
for line in p.stdout.readlines(): | |
line = line.decode("utf-8").strip() | |
# nid is the thread pid as a hex value | |
m = re.search(".*tid=(0x[0-9A-Fa-f]+) nid=(0x[0-9A-Fa-f]+) .*", line) | |
if m: | |
tpid = int(m.group(2), base=16) | |
is_java = check_cmd_java(tpid) | |
print("Found ", m.group(1), m.group(2), tpid, is_java) | |
if is_java: | |
jpids.append(tpid) | |
return jpids | |
def set_timerslack(pids, slack): | |
"""Sets timerslack_ns for each of the provided pids""" | |
for pid in pids: | |
with open(f"/proc/{pid}/timerslack_ns", 'w', encoding="utf-8") as f: | |
f.write(str(slack)) | |
if len(sys.argv) < 2: | |
print("fix-java-timer.py pid [timer res in nanoseconds]") | |
exit() | |
pid = int(sys.argv[1]) | |
timerslack = int(sys.argv[2]) if len(sys.argv) > 2 else 5_000_000 # set to 5 ms by default | |
if check_cmd_java(pid): | |
print(f"Checking process {pid} and trying to set timerslack {timerslack}") | |
jpids = get_java_pids(pid) | |
set_timerslack(jpids, timerslack) | |
else: | |
print(f"Specified pid {pid} is not a java process") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment