Skip to content

Commit 4a8bb70

Browse files
committed
Add LOW_TPS_SPIKES Datapoint
1 parent fd8588c commit 4a8bb70

12 files changed

Lines changed: 140 additions & 5 deletions

File tree

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ public enum WebPermission implements Supplier<String>, Lang {
176176
DATA_SERVER_DEATHS("See Deaths datapoint of servers"),
177177
DATA_NETWORK_DEATHS("See Deaths datapoint of network"),
178178
DATA_SERVER_TPS_AVERAGE("See Average TPS -datapoint of servers"),
179+
DATA_SERVER_TPS_LOW_SPIKES("See Low TPS spikes -datapoint of servers"),
180+
DATA_NETWORK_TPS_LOW_SPIKES("See Low TPS spikes -datapoint of network"),
179181
DATA_SERVER_RAM_AVERAGE("See Average RAM -datapoint of servers"),
180182
DATA_SERVER_MSPT_AVERAGE("See Average MSPT -datapoint of servers"),
181183
DATA_SERVER_CPU_AVERAGE("See Average CPU -datapoint of servers"),

Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ServerPageExporter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ public void exportJSON(Path toDirectory, ServerUUID serverUUID) throws IOExcepti
169169
datapointType + DatapointType.DEATHS + server,
170170
datapointType + DatapointType.UPTIME_CURRENT + server,
171171
datapointType + DatapointType.TPS_AVERAGE + afterMillis + TimeUnit.DAYS.toMillis(7) + server,
172+
datapointType + DatapointType.TPS_LOW_SPIKES + afterMillis + TimeUnit.DAYS.toMillis(7) + server,
172173
datapointType + DatapointType.UNIQUE_PLAYERS_AVERAGE + afterMillis + TimeUnit.DAYS.toMillis(7) + server,
173174
datapointType + DatapointType.NEW_PLAYER_RETENTION + afterMillis + TimeUnit.DAYS.toMillis(7) + server,
174175
// Week comparison

Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/datapoint/DatapointType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public enum DatapointType {
5757
ENTITIES_AVERAGE(EntitiesAverage.class, DatapointCacheKey.TPS),
5858
CHUNKS_AVERAGE(ChunksAverage.class, DatapointCacheKey.TPS),
5959
UNIQUE_PLAYERS_AVERAGE(UniquePlayersPerDayAverage.class, DatapointCacheKey.SESSION),
60-
NEW_PLAYER_RETENTION(NewPlayerRetention.class, DatapointCacheKey.SESSION);
60+
NEW_PLAYER_RETENTION(NewPlayerRetention.class, DatapointCacheKey.SESSION),
61+
TPS_LOW_SPIKES(TPSLowSpikes.class, DatapointCacheKey.TPS);
6162

6263
private final Class<? extends Datapoint<?>> datapointClass;
6364
private final DatapointCacheKey[] cacheKeys;

Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/datapoint/types/DatapointModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,8 @@ public interface DatapointModule {
145145
@Binds
146146
@IntoSet
147147
Datapoint<?> bindNewPlayerRetention(NewPlayerRetention newPlayerRetention);
148+
149+
@Binds
150+
@IntoSet
151+
Datapoint<?> bindTPSLowSpikes(TPSLowSpikes tpsLowSpikes);
148152
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.rendering.json.datapoint.types;
18+
19+
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
20+
import com.djrapitops.plan.delivery.domain.datatransfer.GenericFilter;
21+
import com.djrapitops.plan.delivery.rendering.json.datapoint.Datapoint;
22+
import com.djrapitops.plan.delivery.rendering.json.datapoint.DatapointType;
23+
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
24+
import com.djrapitops.plan.settings.config.PlanConfig;
25+
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
26+
import com.djrapitops.plan.storage.database.DBSystem;
27+
import com.djrapitops.plan.storage.database.queries.objects.TPSQueries;
28+
29+
import javax.inject.Inject;
30+
import javax.inject.Singleton;
31+
import java.util.Optional;
32+
33+
/**
34+
* Datapoint for Low TPS spikes.
35+
* <p>
36+
* Low tps spikes are computed by checking TPS values against a threshold,
37+
* only counting each spike as individual spike once previous spike has recovered.
38+
*
39+
* @author AuroraLS3
40+
*/
41+
@Singleton
42+
public class TPSLowSpikes implements Datapoint<Integer> {
43+
44+
private final PlanConfig config;
45+
private final DBSystem dbSystem;
46+
47+
@Inject
48+
public TPSLowSpikes(PlanConfig config, DBSystem dbSystem) {
49+
this.config = config;
50+
this.dbSystem = dbSystem;
51+
}
52+
53+
@Override
54+
public Optional<Integer> getValue(GenericFilter filter) {
55+
if (filter.getPlayerUUID().isPresent()) {
56+
throw new BadRequestException("TPS_LOW_SPIKES does not support player parameter");
57+
}
58+
return Optional.of(dbSystem.getDatabase().query(TPSQueries.lowTpsSpikes(
59+
config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED),
60+
filter.getAfter(),
61+
filter.getBefore(),
62+
filter.getServerUUIDs()))
63+
);
64+
}
65+
66+
@Override
67+
public WebPermission getPermission(GenericFilter filter) {
68+
if (filter.getPlayerUUID().isPresent()) {
69+
return WebPermission.DATA_PLAYER;
70+
} else if (!filter.getServerUUIDs().isEmpty()) {
71+
return WebPermission.DATA_SERVER_TPS_LOW_SPIKES;
72+
} else {
73+
return WebPermission.DATA_NETWORK_TPS_LOW_SPIKES;
74+
}
75+
}
76+
77+
@Override
78+
public DatapointType getType() {
79+
return DatapointType.TPS_LOW_SPIKES;
80+
}
81+
}

Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,25 @@ public static Query<Map<Integer, Long>> uptime(long after, long before, List<Ser
184184
after, before);
185185
}
186186

187+
public static Query<Integer> lowTpsSpikes(double threshold, long after, long before, List<ServerUUID> serverUUIDs) {
188+
String sql = SELECT + "COUNT(*) as count" + FROM + '(' +
189+
SELECT + SERVER_ID + ',' +
190+
TPS + ',' +
191+
"LAG(" + TPS + ") OVER (PARTITION BY " + SERVER_ID + ORDER_BY + DATE + ") as prev_tps" +
192+
FROM + TABLE_NAME +
193+
WHERE + DATE + ">=?" +
194+
AND + DATE + "<=?" +
195+
(serverUUIDs.isEmpty()
196+
? AND + SERVER_ID + " IN " + ServerTable.selectGameServerIds()
197+
: AND + SERVER_ID + " IN " + ServerTable.selectServerIds(serverUUIDs)) +
198+
") s1" +
199+
WHERE + TPS + ">0" +
200+
AND + TPS + "<?" +
201+
AND + "(prev_tps " + IS_NULL + OR + "prev_tps>=?)";
202+
return db -> db.queryOptional(sql, row -> row.getInt("count"), after, before, threshold, threshold)
203+
.orElse(0);
204+
}
205+
187206
public static Query<List<DateObj<Integer>>> fetchViewPreviewGraphData(ServerUUID serverUUID) {
188207
String sql = SELECT + min(DATE) + " as " + DATE + ',' +
189208
max(PLAYERS_ONLINE) + " as " + PLAYERS_ONLINE +

Plan/common/src/main/java/com/djrapitops/plan/storage/database/sql/tables/ServerTable.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,10 @@ public static String selectServerIds(Collection<ServerUUID> serverUUIDs) {
9595
uuids(serverUUIDs) +
9696
"))";
9797
}
98+
99+
public static String selectGameServerIds() {
100+
return '(' + SELECT + TABLE_NAME + '.' + ID +
101+
FROM + TABLE_NAME +
102+
WHERE + TABLE_NAME + '.' + PROXY + "=0)";
103+
}
98104
}

Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ static Stream<Arguments> testCases() {
246246
Arguments.of("/v1/datapoint?type=TPS_AVERAGE", WebPermission.DATA_NETWORK, 400, 403),
247247
Arguments.of("/v1/datapoint?type=TPS_AVERAGE&server=" + TestConstants.SERVER_UUID_STRING, WebPermission.DATA_SERVER_TPS_AVERAGE, 200, 403),
248248
Arguments.of("/v1/datapoint?type=TPS_AVERAGE&player=" + TestConstants.PLAYER_ONE_UUID_STRING, WebPermission.DATA_PLAYER, 400, 403),
249+
Arguments.of("/v1/datapoint?type=TPS_LOW_SPIKES", WebPermission.DATA_NETWORK_TPS_LOW_SPIKES, 200, 403),
250+
Arguments.of("/v1/datapoint?type=TPS_LOW_SPIKES&server=" + TestConstants.SERVER_UUID_STRING, WebPermission.DATA_SERVER_TPS_LOW_SPIKES, 200, 403),
251+
Arguments.of("/v1/datapoint?type=TPS_LOW_SPIKES&player=" + TestConstants.PLAYER_ONE_UUID_STRING, WebPermission.DATA_PLAYER, 400, 403),
249252
Arguments.of("/v1/datapoint?type=CPU_AVERAGE", WebPermission.DATA_NETWORK, 400, 403),
250253
Arguments.of("/v1/datapoint?type=CPU_AVERAGE&server=" + TestConstants.SERVER_UUID_STRING, WebPermission.DATA_SERVER_CPU_AVERAGE, 200, 403),
251254
Arguments.of("/v1/datapoint?type=CPU_AVERAGE&player=" + TestConstants.PLAYER_ONE_UUID_STRING, WebPermission.DATA_PLAYER, 400, 403),

Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/TPSQueriesTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,20 @@ default void uptimeCalculationMatches() {
154154
assertEquals(expected, result, () -> "Mismatch (" + expected + ", " + result + ") with data " + data);
155155
}
156156

157+
@RepeatedTest(5)
158+
default void lowTpsSpikeCalculationMatches() {
159+
List<TPS> data = RandomData.randomDateOrderedTPS();
160+
for (TPS tps : data) {
161+
execute(DataStoreQueries.storeTPS(serverUUID(), tps));
162+
}
163+
164+
data.sort(new TPSComparator());
165+
double threshold = RandomData.randomInt(3, 18);
166+
Integer expected = new TPSMutator(data).lowTpsSpikeCount(threshold);
167+
Integer result = db().query(TPSQueries.lowTpsSpikes(threshold, Long.MIN_VALUE, Long.MAX_VALUE, List.of(serverUUID())));
168+
assertEquals(expected, result, () -> "Mismatch (" + expected + ", " + result + ") with data " + data);
169+
}
170+
157171
@Test
158172
default void removeEverythingRemovesTPS() {
159173
tpsIsStored();

Plan/common/src/testFixtures/java/utilities/RandomData.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ public static List<TPS> randomDateOrderedTPS() {
9696
int previousPlayers = randomInt(0, 100);
9797
for (int i = 0; i < randomInt(50, 100); i++) {
9898
int randInt = r.nextInt();
99+
double randTps = r.nextDouble() * 20;
99100
long randLong = Math.abs(r.nextLong());
100-
test.add(new TPS(previousTimestamp, randLong, randInt, randLong, randLong, randInt, randInt, randLong));
101+
test.add(new TPS(previousTimestamp, randTps, randInt, randLong, randLong, randInt, randInt, randLong));
101102
boolean reboot = Math.random() < 0.10;
102103
previousTimestamp = previousTimestamp + (reboot ? TimeUnit.MINUTES.toMillis(1) : TimeUnit.MINUTES.toMillis(10));
103104
previousPlayers = Math.max(previousPlayers + r.nextInt(10) - 10, 0);

0 commit comments

Comments
 (0)