Skip to content

Commit 1145783

Browse files
committed
Add runTaskTimer method and improve TPS calculation
1 parent 57a3ad9 commit 1145783

File tree

8 files changed

+120
-60
lines changed

8 files changed

+120
-60
lines changed

bukkit/src/main/java/it/renvins/serverpulse/bukkit/metrics/BukkitTPSRetriever.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void run() {
5555
}.runTaskTimer(plugin, 1L, 1L);
5656
}
5757
private void calculateAverages() {
58-
long sum1m = 0, sum5m = 0, sum15m = 0;
58+
double sum1m = 0, sum5m = 0, sum15m = 0;
5959
int count1m = 0, count5m = 0, count15m = 0;
6060

6161
int i = 0;
@@ -87,7 +87,7 @@ private void calculateAverages() {
8787
tps15m = calculateTPSFromAvgNano(sum15m, count15m);
8888
}
8989

90-
private double calculateTPSFromAvgNano(long totalNano, int count) {
90+
private double calculateTPSFromAvgNano(double totalNano, int count) {
9191
if (count == 0) {
9292
return 20.0;
9393
}

bukkit/src/main/java/it/renvins/serverpulse/bukkit/scheduler/BukkitTaskScheduler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public void runSync(Runnable task) {
1717
plugin.getServer().getScheduler().runTask(plugin, task);
1818
}
1919

20+
@Override
21+
public void runTaskTimer(Runnable task, long delayTicks, long periodTicks) {
22+
plugin.getServer().getScheduler().runTaskTimer(plugin, task, delayTicks, periodTicks);
23+
}
24+
2025
@Override
2126
public void runAsync(Runnable task) {
2227
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, task);

common/src/main/java/it/renvins/serverpulse/common/scheduler/TaskScheduler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ default void runSync(Runnable task) {
1414
throw new UnsupportedOperationException("Synchronous task execution is not supported.");
1515
}
1616

17+
/**
18+
* Runs a task timer synchronously on the main thread.
19+
*
20+
* @param task The task to run
21+
* @throws UnsupportedOperationException if the implementation does not support synchronous task execution
22+
*/
23+
default void runTaskTimer(Runnable task, long delayTicks, long periodTicks) {
24+
throw new UnsupportedOperationException("Synchronous task timer execution is not supported.");
25+
}
26+
1727
/**
1828
* Runs a task asynchronously on a separate thread.
1929
*

fabric/src/main/java/it/renvins/serverpulse/fabric/ServerPulseFabric.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class ServerPulseFabric implements ModInitializer {
4747
private final IPingRetriever pingRetriever;
4848

4949
public ServerPulseFabric() {
50-
this.config = new FabricConfiguration(FabricLoader.getInstance().getConfigDir(), "config.yml");
50+
this.config = new FabricConfiguration(FabricLoader.getInstance().getConfigDir().resolve("serverpulse"), "config.yml");
5151

5252
PulseLogger logger = new FabricLogger();
5353

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,108 @@
11
package it.renvins.serverpulse.fabric.metrics;
22

3-
import java.util.LinkedList;
4-
import java.util.Queue;
5-
63
import it.renvins.serverpulse.api.metrics.ITPSRetriever;
74
import it.renvins.serverpulse.common.scheduler.TaskScheduler;
85
import lombok.RequiredArgsConstructor;
96

107
@RequiredArgsConstructor
118
public class FabricTPSRetriever implements ITPSRetriever {
129

13-
private static final int TICK_PER_SECOND = 20;
14-
private static final int TICK_PER_MIN = TICK_PER_SECOND * 60; // 1200
15-
private static final int TICK_FIVE_MIN = TICK_PER_MIN * 5;
16-
private static final int TICK_FIFTEEN_MIN = TICK_PER_MIN * 15;
10+
private static final int TICKS_PER_SECOND = 20;
1711

18-
private static final int MAX_HISTORY_SIZE = TICK_FIFTEEN_MIN;
12+
// Time windows in seconds
13+
private static final int ONE_MINUTE = 60;
14+
private static final int FIVE_MINUTES = 300;
15+
private static final int FIFTEEN_MINUTES = 900;
1916

20-
private final Queue<Long> tickHistory = new LinkedList<>();
17+
// Maximum history to keep (15 minutes worth of ticks)
18+
private static final int MAX_SAMPLES = FIFTEEN_MINUTES * TICKS_PER_SECOND;
2119

2220
private final TaskScheduler scheduler;
2321

24-
private long lastTickTimeNano = -1;
22+
// Ring buffer for storing tick times
23+
private final long[] tickTimes = new long[MAX_SAMPLES];
24+
private int tickIndex = 0;
25+
private boolean bufferFilled = false;
26+
27+
// Last measurement time
28+
private long lastTickTime = -1;
2529

30+
// Cache for TPS values
2631
private double tps1m = 20.0;
2732
private double tps5m = 20.0;
2833
private double tps15m = 20.0;
2934

3035
@Override
3136
public double[] getTPS() {
32-
calculateAverages();
33-
return new double[]{tps1m, tps5m, tps15m};
37+
return new double[] { tps1m, tps5m, tps15m };
3438
}
3539

3640
public void startTickMonitor() {
37-
lastTickTimeNano = System.nanoTime();
41+
lastTickTime = System.nanoTime();
42+
43+
// Schedule a task to run every server tick
44+
scheduler.runTaskTimer(() -> {
45+
long now = System.nanoTime();
46+
47+
// Only record after first tick
48+
if (lastTickTime > 0) {
49+
// Record this tick's time in nanoseconds
50+
tickTimes[tickIndex] = now - lastTickTime;
3851

39-
scheduler.runTaskTimerAsync(() -> {
40-
long currentTimeNano = System.nanoTime();
41-
long elapsedTime = currentTimeNano - lastTickTimeNano;
42-
lastTickTimeNano = currentTimeNano;
52+
// Update index in ring buffer
53+
tickIndex = (tickIndex + 1) % MAX_SAMPLES;
4354

44-
tickHistory.offer(elapsedTime);
45-
if (tickHistory.size() > MAX_HISTORY_SIZE) {
46-
tickHistory.poll();
55+
// Mark buffer as filled once we wrap around
56+
if (tickIndex == 0) {
57+
bufferFilled = true;
58+
}
59+
60+
// Calculate TPS after recording the tick
61+
updateTPS();
4762
}
63+
64+
lastTickTime = now;
4865
}, 1, 1);
4966
}
5067

51-
public void calculateAverages() {
52-
long sum1m = 0, sum5m = 0, sum15m = 0;
53-
int count1m = 0, count5m = 0, count15m = 0;
54-
55-
Object[] durations = tickHistory.toArray();
56-
for (int i = durations.length - 1; i >= 0; i--) {
57-
if (!(durations[i] instanceof Long)) continue;
58-
long durationNano = (Long) durations[i];
59-
if (i < TICK_PER_MIN) {
60-
sum1m += durationNano;
61-
count1m++;
62-
}
63-
if (i < TICK_FIVE_MIN) {
64-
sum5m += durationNano;
65-
count5m++;
66-
}
67-
if (i < TICK_FIFTEEN_MIN) {
68-
sum15m += durationNano;
69-
count15m++;
70-
} else {
71-
break;
72-
}
73-
}
68+
private void updateTPS() {
69+
// Convert seconds to ticks
70+
int oneMinTicks = ONE_MINUTE * TICKS_PER_SECOND;
71+
int fiveMinTicks = FIVE_MINUTES * TICKS_PER_SECOND;
72+
int fifteenMinTicks = FIFTEEN_MINUTES * TICKS_PER_SECOND;
7473

75-
tps1m = calculateTPSFromAvgNano(sum1m, count1m);
76-
tps5m = calculateTPSFromAvgNano(sum5m, count5m);
77-
tps15m = calculateTPSFromAvgNano(sum15m, count15m);
74+
// Calculate the actual number of samples we have
75+
int sampleCount = bufferFilled ? MAX_SAMPLES : tickIndex;
76+
77+
// Calculate TPS for each time window
78+
tps1m = calculateTPS(Math.min(oneMinTicks, sampleCount));
79+
tps5m = calculateTPS(Math.min(fiveMinTicks, sampleCount));
80+
tps15m = calculateTPS(Math.min(fifteenMinTicks, sampleCount));
7881
}
7982

80-
public double calculateTPSFromAvgNano(long totalNano, int count) {
81-
if (count == 0) {
82-
return 20.0;
83+
private double calculateTPS(int samples) {
84+
if (samples <= 0) {
85+
return 20.0; // Default to 20 TPS if we have no samples
8386
}
84-
double avgTickTimeMillis = (double) totalNano / count / 1_000_000.0;
85-
if (avgTickTimeMillis <= 0) {
86-
return 20.0;
87+
88+
// Calculate total time for the samples
89+
long totalTime = 0;
90+
91+
// Start from the most recent sample and go backwards
92+
for (int i = 0; i < samples; i++) {
93+
// Calculate index in the circular buffer, accounting for wrap-around
94+
int idx = (tickIndex - 1 - i + MAX_SAMPLES) % MAX_SAMPLES;
95+
totalTime += tickTimes[idx];
8796
}
88-
// TPS: 1 second (1000ms) / avg tick time (ms)
89-
double tps = 1000.0 / avgTickTimeMillis;
90-
return Math.min(tps, 20.0);
97+
98+
// Calculate TPS:
99+
// - totalTime is in nanoseconds for 'samples' ticks
100+
// - Convert to seconds to get time for 'samples' ticks
101+
// - Then calculate how many ticks would occur in 1 second
102+
double timeInSeconds = totalTime / 1_000_000_000.0;
103+
double ticksPerSecond = samples / timeInSeconds;
104+
105+
// Clamp to maximum of 20 TPS
106+
return Math.min(ticksPerSecond, 20.0);
91107
}
92-
}
108+
}

fabric/src/main/java/it/renvins/serverpulse/fabric/platform/FabricPlatform.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import lombok.RequiredArgsConstructor;
1010
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
1111
import net.fabricmc.loader.api.FabricLoader;
12+
import net.minecraft.entity.Entity;
1213
import net.minecraft.server.world.ServerWorld;
14+
import net.minecraft.util.TypeFilter;
1315

1416
@RequiredArgsConstructor
1517
public class FabricPlatform implements Platform {
@@ -36,7 +38,7 @@ public Map<String, WorldData> getWorldsData() {
3638
Map<String, WorldData> worldsData = new HashMap<>();
3739
for (ServerWorld world : mod.getServer().getWorlds()) {
3840
WorldData worldData = new WorldData(
39-
world.getEntitiesByType(null, entity -> true).size(),
41+
world.getEntitiesByType(TypeFilter.instanceOf(Entity.class), entity -> true).size(),
4042
world.getChunkManager().getLoadedChunkCount());
4143

4244
worldsData.put(world.getRegistryKey().getValue().toString(), worldData);

fabric/src/main/java/it/renvins/serverpulse/fabric/task/FabricScheduler.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public void runSync(Runnable task) {
2424
tasks.add(taskWrapper);
2525
}
2626

27+
@Override
28+
public void runTaskTimer(Runnable task, long delayTicks, long periodTicks) {
29+
FabricTask fabricTask = new FabricTask(task, false, delayTicks, periodTicks);
30+
tasks.add(fabricTask);
31+
}
32+
2733
@Override
2834
public void runAsync(Runnable task) {
2935
CompletableFuture.runAsync(task);

fabric/src/main/resources/config.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
metrics:
2+
interval: 5
3+
influxdb:
4+
url: http://localhost:8086 # The URL of the InfluxDB API
5+
org: my-org # The organization where metrics are stored
6+
bucket: metrics_db # The bucket where metrics are stored
7+
token: my-token # The token to access the InfluxDB API (WRITE AND READ ACCESS)
8+
table: minecraft_stats # The table where metrics are stored
9+
tags:
10+
server: "fabric1"
11+
messages:
12+
noPerms: "&7[&bServer&7Pulse] &7You don't have &bpermission &7to use this &bcommand&7."
13+
reloadConfig: "&7[&bServer&7Pulse] &7Configuration &breloaded&7."
14+
reloadConfigError: "&7[&bServer&7Pulse] &7Error &breloading &7configuration..."
15+
noArgs: "&7[&bServer&7Pulse] &7You need to specify a &bcommand&7: &breload&7, &bstatus&7."
16+
playerOnly: "&7[&bServer&7Pulse] &7This command can only be used by &bplayers&7."
17+
noCommand: "&7[&bServer&7Pulse] &7This command is not &bavailable&7."
18+
reloadConfigUsage: "&7[&bServer&7Pulse] &7Usage: &b/serverpulse reload&7."
19+
statusConfigUsage: "&7[&bServer&7Pulse] &7Usage: &b/serverpulse status&7."
20+
statusConnected: "&7[&bServer&7Pulse] &7Connected to &bInfluxDB&7."
21+
statusNotConnected: "&7[&bServer&7Pulse] &7Not connected to &bInfluxDB&7."

0 commit comments

Comments
 (0)