1
1
package it .renvins .serverpulse .fabric .metrics ;
2
2
3
- import java .util .LinkedList ;
4
- import java .util .Queue ;
5
-
6
3
import it .renvins .serverpulse .api .metrics .ITPSRetriever ;
7
4
import it .renvins .serverpulse .common .scheduler .TaskScheduler ;
8
5
import lombok .RequiredArgsConstructor ;
9
6
10
7
@ RequiredArgsConstructor
11
8
public class FabricTPSRetriever implements ITPSRetriever {
12
9
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 ;
17
11
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 ;
19
16
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 ;
21
19
22
20
private final TaskScheduler scheduler ;
23
21
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 ;
25
29
30
+ // Cache for TPS values
26
31
private double tps1m = 20.0 ;
27
32
private double tps5m = 20.0 ;
28
33
private double tps15m = 20.0 ;
29
34
30
35
@ Override
31
36
public double [] getTPS () {
32
- calculateAverages ();
33
- return new double []{tps1m , tps5m , tps15m };
37
+ return new double [] { tps1m , tps5m , tps15m };
34
38
}
35
39
36
40
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 ;
38
51
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 ;
43
54
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 ();
47
62
}
63
+
64
+ lastTickTime = now ;
48
65
}, 1 , 1 );
49
66
}
50
67
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 ;
74
73
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 ));
78
81
}
79
82
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
83
86
}
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 ];
87
96
}
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 );
91
107
}
92
- }
108
+ }
0 commit comments