1
1
package it .renvins .serverpulse .fabric .metrics ;
2
2
3
+ import java .util .LinkedList ;
4
+ import java .util .Queue ;
5
+
3
6
import it .renvins .serverpulse .api .metrics .ITPSRetriever ;
4
- import lombok .RequiredArgsConstructor ;
5
7
import net .fabricmc .fabric .api .event .lifecycle .v1 .ServerTickEvents ;
6
8
7
- @ RequiredArgsConstructor
8
9
public class FabricTPSRetriever implements ITPSRetriever {
9
10
10
11
private static final int TICKS_PER_SECOND = 20 ;
12
+ private static final int ONE_MINUTE_TICKS = 60 * TICKS_PER_SECOND ; // 1200
13
+ private static final int FIVE_MINUTES_TICKS = 5 * ONE_MINUTE_TICKS ; // 6000
14
+ private static final int FIFTEEN_MINUTES_TICKS = 15 * ONE_MINUTE_TICKS ; // 18000
11
15
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 ;
16
-
17
- // Maximum history to keep (15 minutes worth of ticks)
18
- private static final int MAX_SAMPLES = FIFTEEN_MINUTES * TICKS_PER_SECOND ;
16
+ private static final int MAX_HISTORY_SIZE = FIFTEEN_MINUTES_TICKS ;
19
17
20
- // Ring buffer for storing tick times
21
- private final long [] tickTimes = new long [MAX_SAMPLES ];
22
- private int tickIndex = 0 ;
23
- private boolean bufferFilled = false ;
18
+ // Queue for FIFO
19
+ private final Queue <Long > tickDurations = new LinkedList <>();
20
+ private long lastTickTimeNano = -1 ;
24
21
25
- // Last measurement time
26
- private long lastTickTime = -1 ;
27
-
28
- // Cache for TPS values
29
22
private double tps1m = 20.0 ;
30
23
private double tps5m = 20.0 ;
31
24
private double tps15m = 20.0 ;
32
25
33
26
@ Override
34
27
public double [] getTPS () {
35
- return new double [] { tps1m , tps5m , tps15m };
28
+ calculateAverages ();
29
+ return new double []{tps1m , tps5m , tps15m };
36
30
}
37
31
38
32
public void startTickMonitor () {
39
- lastTickTime = System .nanoTime ();
40
-
41
- // Use ServerTickEvents instead of a scheduled task
42
- ServerTickEvents .END_SERVER_TICK .register (server -> {
43
- long now = System .nanoTime ();
33
+ lastTickTimeNano = System .nanoTime ();
44
34
45
- // Only record after first tick
46
- if ( lastTickTime > 0 ) {
47
- // Record this tick's time in nanoseconds
48
- tickTimes [ tickIndex ] = now - lastTickTime ;
35
+ ServerTickEvents . END_SERVER_TICK . register ( tick -> {
36
+ long currentTimeNano = System . nanoTime ();
37
+ long elapsedNano = currentTimeNano - lastTickTimeNano ;
38
+ lastTickTimeNano = currentTimeNano ;
49
39
50
- // Update index in ring buffer
51
- tickIndex = (tickIndex + 1 ) % MAX_SAMPLES ;
52
-
53
- // Mark buffer as filled once we wrap around
54
- if (tickIndex == 0 ) {
55
- bufferFilled = true ;
40
+ tickDurations .offer (elapsedNano );
41
+ if (tickDurations .size () > MAX_HISTORY_SIZE ) {
42
+ tickDurations .poll ();
56
43
}
44
+ });
45
+ }
57
46
58
- // Calculate TPS after recording the tick
59
- updateTPS () ;
60
- }
47
+ private void calculateAverages () {
48
+ double sum1m = 0 , sum5m = 0 , sum15m = 0 ;
49
+ int count1m = 0 , count5m = 0 , count15m = 0 ;
61
50
62
- lastTickTime = now ;
63
- });
64
- }
51
+ int i = 0 ;
52
+ Object [] durationsArray = tickDurations .toArray ();
65
53
66
- private void updateTPS () {
67
- // Convert seconds to ticks
68
- int oneMinTicks = ONE_MINUTE * TICKS_PER_SECOND ;
69
- int fiveMinTicks = FIVE_MINUTES * TICKS_PER_SECOND ;
70
- int fifteenMinTicks = FIFTEEN_MINUTES * TICKS_PER_SECOND ;
54
+ for (int j = durationsArray .length - 1 ; j >= 0 ; j --) {
55
+ if (!(durationsArray [j ] instanceof Long )) continue ;
56
+ long durationNano = (Long ) durationsArray [j ];
71
57
72
- // Calculate the actual number of samples we have
73
- int sampleCount = bufferFilled ? MAX_SAMPLES : tickIndex ;
58
+ if (i < ONE_MINUTE_TICKS ) {
59
+ sum1m += durationNano ;
60
+ count1m ++;
61
+ }
62
+ if (i < FIVE_MINUTES_TICKS ) {
63
+ sum5m += durationNano ;
64
+ count5m ++;
65
+ }
66
+ if (i < FIFTEEN_MINUTES_TICKS ) {
67
+ sum15m += durationNano ;
68
+ count15m ++;
69
+ } else {
70
+ break ;
71
+ }
72
+ i ++;
73
+ }
74
74
75
- // Calculate TPS for each time window
76
- tps1m = calculateTPS (Math .min (oneMinTicks , sampleCount ));
77
- tps5m = calculateTPS (Math .min (fiveMinTicks , sampleCount ));
78
- tps15m = calculateTPS (Math .min (fifteenMinTicks , sampleCount ));
75
+ tps1m = calculateTPSFromAvgNano (sum1m , count1m );
76
+ tps5m = calculateTPSFromAvgNano (sum5m , count5m );
77
+ tps15m = calculateTPSFromAvgNano (sum15m , count15m );
79
78
}
80
79
81
- private double calculateTPS ( int samples ) {
82
- if (samples < = 0 ) {
83
- return 20.0 ; // Default to 20 TPS if we have no samples
80
+ private double calculateTPSFromAvgNano ( double totalNano , int count ) {
81
+ if (count = = 0 ) {
82
+ return 20.0 ;
84
83
}
85
-
86
- // Calculate total time for the samples
87
- long totalTime = 0 ;
88
-
89
- // Start from the most recent sample and go backwards
90
- for (int i = 0 ; i < samples ; i ++) {
91
- // Calculate index in the circular buffer, accounting for wrap-around
92
- int idx = (tickIndex - 1 - i + MAX_SAMPLES ) % MAX_SAMPLES ;
93
- totalTime += tickTimes [idx ];
84
+ double avgTickTimeMillis = (double ) totalNano / count / 1_000_000.0 ;
85
+ if (avgTickTimeMillis <= 0 ) {
86
+ return 20.0 ;
94
87
}
95
-
96
- // Calculate TPS:
97
- // - totalTime is in nanoseconds for 'samples' ticks
98
- // - Convert to seconds to get time for 'samples' ticks
99
- // - Then calculate how many ticks would occur in 1 second
100
- double timeInSeconds = totalTime / 1_000_000_000.0 ;
101
- double ticksPerSecond = samples / timeInSeconds ;
102
-
103
- // Clamp to maximum of 20 TPS
104
- return Math .min (ticksPerSecond , 20.0 );
88
+ // TPS: 1 second (1000ms) / avg tick time (ms)
89
+ double tps = 1000.0 / avgTickTimeMillis ;
90
+ return Math .min (tps , 20.0 );
105
91
}
106
- }
92
+ }
0 commit comments