mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-09-28 22:47:38 +02:00
Network performance tab (#2009)
* Fixed disk medium threshold not showing color * Added 'serverName' and 'serverUUID' to optimizedPerformance endpoint * Added /v1/network/listServers endpoint * Added /v1/network/performanceOverview?servers endpoint * Hide negative values from performance graphs * Allow json cache bypass by not providing timestamp parameter in URIQuery * Ignore negative values in low tps spike count * Added (Unavailable with Export) to exported network html performance tab title Affects issues: - Close #1693
This commit is contained in:
parent
c22874df34
commit
13823c044a
@ -170,7 +170,7 @@ public class TPSMutator {
|
||||
|
||||
for (TPS tpsObj : tpsData) {
|
||||
double tps = tpsObj.getTicksPerSecond();
|
||||
if (tps < threshold) {
|
||||
if (0 <= tps && tps < threshold) {
|
||||
if (!wasLow) {
|
||||
spikeCount++;
|
||||
wasLow = true;
|
||||
|
@ -98,8 +98,8 @@ public class NetworkPageExporter extends FileExporter {
|
||||
|
||||
// Fixes refreshingJsonRequest ignoring old data of export
|
||||
String html = StringUtils.replaceEach(page.toHtml(),
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview', true);"},
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview');"});
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview', true);", "· Performance"},
|
||||
new String[]{"loadPlayersOnlineGraph, 'network-overview');", "· Performance (Unavailable with Export)"});
|
||||
|
||||
export(to, exportPaths.resolveExportPaths(html));
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Factory with different JSON creation methods placed to a single class.
|
||||
@ -258,4 +259,16 @@ public class JSONFactory {
|
||||
return tableEntries;
|
||||
}
|
||||
|
||||
public Map<String, Object> listServers() {
|
||||
Collection<Server> servers = dbSystem.getDatabase().query(ServerQueries.fetchPlanServerInformationCollection());
|
||||
return Maps.builder(String.class, Object.class)
|
||||
.put("servers", servers.stream()
|
||||
.map(server -> Maps.builder(String.class, Object.class)
|
||||
.put("serverUUID", server.getUuid().toString())
|
||||
.put("serverName", server.getIdentifiableName())
|
||||
.put("proxy", server.isProxy())
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -29,9 +29,12 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.gathering.domain.FinishedSession;
|
||||
import com.djrapitops.plan.gathering.domain.Ping;
|
||||
import com.djrapitops.plan.gathering.domain.WorldTimes;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
@ -119,7 +122,9 @@ public class GraphJSONCreator {
|
||||
"}}";
|
||||
}
|
||||
|
||||
public Map<String, Object> optimizedPerformanceGraphJSON(ServerUUID serverUUID) {
|
||||
public Map<String, Object> optimizedPerformanceGraphJSON(ServerUUID serverUUID, URIQuery query) {
|
||||
long after = getAfter(query); // TODO Implement if performance issues become apparent.
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long twoMonthsAgo = now - TimeUnit.DAYS.toMillis(60);
|
||||
long monthAgo = now - TimeUnit.DAYS.toMillis(30);
|
||||
@ -131,6 +136,10 @@ public class GraphJSONCreator {
|
||||
TPSMutator lowResolutionData = new TPSMutator(db.query(TPSQueries.fetchTPSDataOfServerInResolution(twoMonthsAgo, monthAgo, lowResolution, serverUUID)));
|
||||
TPSMutator highResolutionData = new TPSMutator(db.query(TPSQueries.fetchTPSDataOfServer(monthAgo, now, serverUUID)));
|
||||
|
||||
String serverName = db.query(ServerQueries.fetchServerMatchingIdentifier(serverUUID))
|
||||
.map(Server::getIdentifiableName)
|
||||
.orElse(serverUUID.toString());
|
||||
|
||||
List<Number[]> values = lowestResolutionData.toArrays(new LineGraph.GapStrategy(
|
||||
config.isTrue(DisplaySettings.GAPS_IN_GRAPH_DATA),
|
||||
lowestResolution + TimeUnit.MINUTES.toMillis(1),
|
||||
@ -172,9 +181,21 @@ public class GraphJSONCreator {
|
||||
.put("diskThresholdMed", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_MED))
|
||||
.put("diskThresholdHigh", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_HIGH))
|
||||
.build())
|
||||
.put("serverName", serverName)
|
||||
.put("serverUUID", serverUUID)
|
||||
.build();
|
||||
}
|
||||
|
||||
private long getAfter(URIQuery query) {
|
||||
try {
|
||||
return query.get("after")
|
||||
.map(Long::parseLong)
|
||||
.orElse(0L) - 500L; // Some headroom for out-of-sync clock.
|
||||
} catch (NumberFormatException badType) {
|
||||
throw new BadRequestException("'after': " + badType.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public String playersOnlineGraph(ServerUUID serverUUID) {
|
||||
Database db = dbSystem.getDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
|
@ -25,6 +25,7 @@ import com.djrapitops.plan.utilities.UnitSemaphoreAccessLock;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
@ -62,7 +63,7 @@ public class AsyncJSONResolverService {
|
||||
}
|
||||
|
||||
public <T> JSONStorage.StoredJSON resolve(
|
||||
long newerThanTimestamp, DataID dataID, ServerUUID serverUUID, Function<ServerUUID, T> creator
|
||||
Optional<Long> newerThanTimestamp, DataID dataID, ServerUUID serverUUID, Function<ServerUUID, T> creator
|
||||
) {
|
||||
String identifier = dataID.of(serverUUID);
|
||||
Supplier<T> jsonCreator = () -> creator.apply(serverUUID);
|
||||
@ -71,24 +72,29 @@ public class AsyncJSONResolverService {
|
||||
|
||||
|
||||
public <T> JSONStorage.StoredJSON resolve(
|
||||
long newerThanTimestamp, DataID dataID, Supplier<T> jsonCreator
|
||||
Optional<Long> newerThanTimestamp, DataID dataID, Supplier<T> jsonCreator
|
||||
) {
|
||||
String identifier = dataID.name();
|
||||
return getStoredOrCreateJSON(newerThanTimestamp, identifier, jsonCreator);
|
||||
}
|
||||
|
||||
private <T> JSONStorage.StoredJSON getStoredOrCreateJSON(
|
||||
long timestamp, String identifier, Supplier<T> jsonCreator
|
||||
Optional<Long> givenTimestamp, String identifier, Supplier<T> jsonCreator
|
||||
) {
|
||||
JSONStorage.StoredJSON storedJSON = getNewFromCache(timestamp, identifier);
|
||||
JSONStorage.StoredJSON storedJSON = null;
|
||||
Future<JSONStorage.StoredJSON> updatedJSON = null;
|
||||
if (givenTimestamp.isPresent()) {
|
||||
long timestamp = givenTimestamp.get();
|
||||
storedJSON = getNewFromCache(timestamp, identifier);
|
||||
if (storedJSON != null) return storedJSON;
|
||||
|
||||
// No new enough version, let's refresh and send old version of the file
|
||||
Future<JSONStorage.StoredJSON> updatedJSON = scheduleJSONForUpdate(timestamp, identifier, jsonCreator);
|
||||
|
||||
updatedJSON = scheduleJSONForUpdate(timestamp, identifier, jsonCreator);
|
||||
storedJSON = getOldFromCache(timestamp, identifier);
|
||||
}
|
||||
|
||||
if (storedJSON != null) {
|
||||
return storedJSON;
|
||||
return storedJSON; // Found old from cache
|
||||
} else {
|
||||
// Update not performed if the last update was recent and the file is deleted before next update
|
||||
// Fall back to waiting for the updated file if old version of the file doesn't exist.
|
||||
|
@ -49,8 +49,8 @@ public enum DataID {
|
||||
PLAYERBASE_OVERVIEW,
|
||||
PERFORMANCE_OVERVIEW,
|
||||
EXTENSION_NAV,
|
||||
EXTENSION_TABS
|
||||
;
|
||||
EXTENSION_TABS,
|
||||
LIST_SERVERS;
|
||||
|
||||
public String of(ServerUUID serverUUID) {
|
||||
return name() + '-' + serverUUID;
|
||||
|
@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.DataID;
|
||||
@ -87,14 +88,14 @@ public class GraphsJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getGraphJSON(Request request, DataID dataID) {
|
||||
long timestamp = Identifiers.getTimestamp(request);
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
|
||||
JSONStorage.StoredJSON storedJSON;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
storedJSON = jsonResolverService.resolve(
|
||||
timestamp, dataID, serverUUID,
|
||||
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID)
|
||||
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery())
|
||||
);
|
||||
} else {
|
||||
// Assume network
|
||||
@ -138,12 +139,12 @@ public class GraphsJSONResolver implements Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID) {
|
||||
private Object generateGraphDataJSONOfType(DataID id, ServerUUID serverUUID, URIQuery query) {
|
||||
switch (id) {
|
||||
case GRAPH_PERFORMANCE:
|
||||
return graphJSON.performanceGraphJSON(serverUUID);
|
||||
case GRAPH_OPTIMIZED_PERFORMANCE:
|
||||
return graphJSON.optimizedPerformanceGraphJSON(serverUUID);
|
||||
return graphJSON.optimizedPerformanceGraphJSON(serverUUID, query);
|
||||
case GRAPH_ONLINE:
|
||||
return graphJSON.playersOnlineGraph(serverUUID);
|
||||
case GRAPH_UNIQUE_NEW:
|
||||
|
@ -44,7 +44,8 @@ public class NetworkJSONResolver {
|
||||
AsyncJSONResolverService asyncJSONResolverService, JSONFactory jsonFactory,
|
||||
NetworkOverviewJSONCreator networkOverviewJSONCreator,
|
||||
NetworkPlayerBaseOverviewJSONCreator networkPlayerBaseOverviewJSONCreator,
|
||||
NetworkSessionsOverviewJSONCreator networkSessionsOverviewJSONCreator
|
||||
NetworkSessionsOverviewJSONCreator networkSessionsOverviewJSONCreator,
|
||||
NetworkPerformanceJSONResolver networkPerformanceJSONResolver
|
||||
) {
|
||||
this.asyncJSONResolverService = asyncJSONResolverService;
|
||||
resolver = CompositeResolver.builder()
|
||||
@ -53,6 +54,8 @@ public class NetworkJSONResolver {
|
||||
.add("sessionsOverview", forJSON(DataID.SESSIONS_OVERVIEW, networkSessionsOverviewJSONCreator))
|
||||
.add("servers", forJSON(DataID.SERVERS, jsonFactory::serversAsJSONMaps))
|
||||
.add("pingTable", forJSON(DataID.PING_TABLE, jsonFactory::pingPerGeolocation))
|
||||
.add("listServers", forJSON(DataID.LIST_SERVERS, jsonFactory::listServers))
|
||||
.add("performanceOverview", networkPerformanceJSONResolver)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver.json;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
|
||||
import com.djrapitops.plan.gathering.domain.TPS;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.GenericLang;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.TPSQueries;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Creates JSON payload for /server-page Performance tab.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class NetworkPerformanceJSONResolver implements Resolver {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Locale locale;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
private final Formatter<Double> decimals;
|
||||
private final Formatter<Long> timeAmount;
|
||||
private final Formatter<Double> percentage;
|
||||
private final Formatter<Double> byteSize;
|
||||
|
||||
@Inject
|
||||
public NetworkPerformanceJSONResolver(
|
||||
PlanConfig config,
|
||||
Locale locale,
|
||||
DBSystem dbSystem,
|
||||
Formatters formatters
|
||||
) {
|
||||
this.config = config;
|
||||
this.locale = locale;
|
||||
this.dbSystem = dbSystem;
|
||||
|
||||
decimals = formatters.decimals();
|
||||
percentage = formatters.percentage();
|
||||
timeAmount = formatters.timeAmount();
|
||||
byteSize = formatters.byteSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return request.getUser().orElse(new WebUser("")).hasPermission("page.network");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
List<ServerUUID> serverUUIDs = request.getQuery().get("servers")
|
||||
.map(this::getUUIDList)
|
||||
.orElse(Collections.emptyList())
|
||||
.stream().map(ServerUUID::from)
|
||||
.collect(Collectors.toList());
|
||||
return Optional.of(Response.builder()
|
||||
.setJSONContent(createJSONAsMap(serverUUIDs))
|
||||
.build());
|
||||
}
|
||||
|
||||
private List<UUID> getUUIDList(String jsonString) {
|
||||
return new Gson().fromJson(jsonString, new TypeToken<List<UUID>>() {}.getType());
|
||||
}
|
||||
|
||||
public Map<String, Object> createJSONAsMap(Collection<ServerUUID> serverUUIDs) {
|
||||
Map<String, Object> serverOverview = new HashMap<>();
|
||||
Database db = dbSystem.getDatabase();
|
||||
long now = System.currentTimeMillis();
|
||||
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
||||
Map<Integer, List<TPS>> tpsData = db.query(TPSQueries.fetchTPSDataOfServers(monthAgo, now, serverUUIDs));
|
||||
|
||||
serverOverview.put("numbers", createNumbersMap(tpsData));
|
||||
return serverOverview;
|
||||
}
|
||||
|
||||
private Map<String, Object> createNumbersMap(Map<Integer, List<TPS>> tpsData) {
|
||||
long now = System.currentTimeMillis();
|
||||
long dayAgo = now - TimeUnit.DAYS.toMillis(1L);
|
||||
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);
|
||||
|
||||
Map<String, Object> numbers = new HashMap<>();
|
||||
|
||||
List<TPS> tpsDataOfAllServers = new ArrayList<>();
|
||||
tpsData.values().forEach(tpsDataOfAllServers::addAll);
|
||||
TPSMutator tpsDataMonth = new TPSMutator(tpsDataOfAllServers);
|
||||
TPSMutator tpsDataWeek = tpsDataMonth.filterDataBetween(weekAgo, now);
|
||||
TPSMutator tpsDataDay = tpsDataWeek.filterDataBetween(dayAgo, now);
|
||||
|
||||
Map<Integer, TPSMutator> mutatorsOfServersMonth = new HashMap<>();
|
||||
Map<Integer, TPSMutator> mutatorsOfServersWeek = new HashMap<>();
|
||||
Map<Integer, TPSMutator> mutatorsOfServersDay = new HashMap<>();
|
||||
for (Map.Entry<Integer, List<TPS>> entry : tpsData.entrySet()) {
|
||||
TPSMutator mutator = new TPSMutator(entry.getValue());
|
||||
mutatorsOfServersMonth.put(entry.getKey(), mutator);
|
||||
mutatorsOfServersWeek.put(entry.getKey(), mutator.filterDataBetween(weekAgo, now));
|
||||
mutatorsOfServersDay.put(entry.getKey(), mutator.filterDataBetween(dayAgo, now));
|
||||
}
|
||||
|
||||
Integer tpsThreshold = config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED);
|
||||
numbers.put("low_tps_spikes_30d", tpsDataMonth.lowTpsSpikeCount(tpsThreshold));
|
||||
numbers.put("low_tps_spikes_7d", tpsDataWeek.lowTpsSpikeCount(tpsThreshold));
|
||||
numbers.put("low_tps_spikes_24h", tpsDataDay.lowTpsSpikeCount(tpsThreshold));
|
||||
|
||||
long downtimeMonth = getTotalDowntime(mutatorsOfServersMonth);
|
||||
long downtimeWeek = getTotalDowntime(mutatorsOfServersWeek);
|
||||
long downtimeDay = getTotalDowntime(mutatorsOfServersDay);
|
||||
numbers.put("server_downtime_30d", timeAmount.apply(downtimeMonth));
|
||||
numbers.put("server_downtime_7d", timeAmount.apply(downtimeWeek));
|
||||
numbers.put("server_downtime_24h", timeAmount.apply(downtimeDay));
|
||||
|
||||
if (!tpsData.isEmpty()) {
|
||||
numbers.put("avg_server_downtime_30d", timeAmount.apply(downtimeMonth / tpsData.size()));
|
||||
numbers.put("avg_server_downtime_7d", timeAmount.apply(downtimeWeek / tpsData.size()));
|
||||
numbers.put("avg_server_downtime_24h", timeAmount.apply(downtimeDay / tpsData.size()));
|
||||
} else {
|
||||
numbers.put("avg_server_downtime_30d", "-");
|
||||
numbers.put("avg_server_downtime_7d", "-");
|
||||
numbers.put("avg_server_downtime_24h", "-");
|
||||
}
|
||||
|
||||
numbers.put("tps_30d", format(tpsDataMonth.averageTPS()));
|
||||
numbers.put("tps_7d", format(tpsDataWeek.averageTPS()));
|
||||
numbers.put("tps_24h", format(tpsDataDay.averageTPS()));
|
||||
numbers.put("cpu_30d", formatPercentage(tpsDataMonth.averageCPU()));
|
||||
numbers.put("cpu_7d", formatPercentage(tpsDataWeek.averageCPU()));
|
||||
numbers.put("cpu_24h", formatPercentage(tpsDataDay.averageCPU()));
|
||||
numbers.put("ram_30d", formatBytes(tpsDataMonth.averageRAM()));
|
||||
numbers.put("ram_7d", formatBytes(tpsDataWeek.averageRAM()));
|
||||
numbers.put("ram_24h", formatBytes(tpsDataDay.averageRAM()));
|
||||
numbers.put("entities_30d", format((int) tpsDataMonth.averageEntities()));
|
||||
numbers.put("entities_7d", format((int) tpsDataWeek.averageEntities()));
|
||||
numbers.put("entities_24h", format((int) tpsDataDay.averageEntities()));
|
||||
numbers.put("chunks_30d", format((int) tpsDataMonth.averageChunks()));
|
||||
numbers.put("chunks_7d", format((int) tpsDataWeek.averageChunks()));
|
||||
numbers.put("chunks_24h", format((int) tpsDataDay.averageChunks()));
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
private long getTotalDowntime(Map<Integer, TPSMutator> mutatorsOfServersMonth) {
|
||||
long downTime = 0L;
|
||||
for (TPSMutator tpsMutator : mutatorsOfServersMonth.values()) {
|
||||
downTime += tpsMutator.serverDownTime();
|
||||
}
|
||||
return downTime;
|
||||
}
|
||||
|
||||
private String format(double value) {
|
||||
return value != -1 ? decimals.apply(value) : locale.get(GenericLang.UNAVAILABLE).toString();
|
||||
}
|
||||
|
||||
private String formatBytes(double value) {
|
||||
return value != -1 ? byteSize.apply(value) : locale.get(GenericLang.UNAVAILABLE).toString();
|
||||
}
|
||||
|
||||
private String formatPercentage(double value) {
|
||||
return value != -1 ? percentage.apply(value / 100.0) : locale.get(GenericLang.UNAVAILABLE).toString();
|
||||
}
|
||||
|
||||
}
|
@ -68,7 +68,7 @@ public class PlayerKillsJSONResolver implements Resolver {
|
||||
|
||||
private Response getResponse(Request request) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||
long timestamp = Identifiers.getTimestamp(request);
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
JSONStorage.StoredJSON storedJSON = jsonResolverService.resolve(timestamp, DataID.KILLS, serverUUID,
|
||||
theUUID -> Collections.singletonMap("player_kills", jsonFactory.serverPlayerKillsAsJSONMap(theUUID))
|
||||
);
|
||||
|
@ -78,7 +78,7 @@ public class PlayersTableJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getStoredJSON(Request request) {
|
||||
long timestamp = Identifiers.getTimestamp(request);
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
JSONStorage.StoredJSON storedJSON;
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
|
||||
|
@ -74,7 +74,7 @@ public class SessionsJSONResolver implements Resolver {
|
||||
}
|
||||
|
||||
private JSONStorage.StoredJSON getStoredJSON(Request request) {
|
||||
long timestamp = Identifiers.getTimestamp(request);
|
||||
Optional<Long> timestamp = Identifiers.getTimestamp(request);
|
||||
if (request.getQuery().get("server").isPresent()) {
|
||||
ServerUUID serverUUID = identifiers.getServerUUID(request);
|
||||
return jsonResolverService.resolve(timestamp, DataID.SESSIONS, serverUUID,
|
||||
|
@ -170,15 +170,21 @@ public class TPS implements DateHolder {
|
||||
}
|
||||
|
||||
public Number[] toArray() {
|
||||
double tps = getTicksPerSecond();
|
||||
double cpu = getCPUUsage();
|
||||
long ram = getUsedMemory();
|
||||
int entities = getEntityCount();
|
||||
int chunks = getChunksLoaded();
|
||||
long disk = getFreeDiskSpace();
|
||||
return new Number[]{
|
||||
getDate(),
|
||||
getPlayers(),
|
||||
getTicksPerSecond(),
|
||||
getCPUUsage(),
|
||||
getUsedMemory(),
|
||||
getEntityCount(),
|
||||
getChunksLoaded(),
|
||||
getFreeDiskSpace()
|
||||
tps >= 0 ? tps : null,
|
||||
cpu >= 0 ? cpu : null,
|
||||
ram >= 0 ? ram : null,
|
||||
entities >= 0 ? entities : null,
|
||||
chunks >= 0 ? chunks : null,
|
||||
disk >= 0 ? disk : null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -105,16 +105,16 @@ public class Identifiers {
|
||||
return uuidUtility.getUUIDOf(name);
|
||||
}
|
||||
|
||||
public static long getTimestamp(Request request) {
|
||||
public static Optional<Long> getTimestamp(Request request) {
|
||||
try {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long timestamp = request.getQuery().get("timestamp")
|
||||
.map(Long::parseLong)
|
||||
.orElse(currentTime);
|
||||
if (currentTime + TimeUnit.SECONDS.toMillis(10L) < timestamp) {
|
||||
return currentTime;
|
||||
return Optional.empty();
|
||||
}
|
||||
return timestamp;
|
||||
return Optional.of(timestamp);
|
||||
} catch (NumberFormatException nonNumberTimestamp) {
|
||||
throw new BadRequestException("'timestamp' was not a number: " + nonNumberTimestamp.getMessage());
|
||||
}
|
||||
|
@ -445,4 +445,31 @@ public class TPSQueries {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Query<Map<Integer, List<TPS>>> fetchTPSDataOfServers(long after, long before, Collection<ServerUUID> serverUUIDs) {
|
||||
String sql = SELECT + "*" + FROM + TABLE_NAME +
|
||||
WHERE + SERVER_ID + " IN " + ServerTable.selectServerIds(serverUUIDs) +
|
||||
AND + DATE + ">=?" +
|
||||
AND + DATE + "<=?" +
|
||||
ORDER_BY + DATE;
|
||||
System.out.println(sql);
|
||||
return new QueryStatement<Map<Integer, List<TPS>>>(sql, 50000) {
|
||||
@Override
|
||||
public void prepare(PreparedStatement statement) throws SQLException {
|
||||
statement.setLong(1, after);
|
||||
statement.setLong(2, before);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, List<TPS>> processResults(ResultSet set) throws SQLException {
|
||||
Map<Integer, List<TPS>> data = new HashMap<>();
|
||||
while (set.next()) {
|
||||
int serverId = set.getInt(SERVER_ID);
|
||||
data.computeIfAbsent(serverId, Lists::create)
|
||||
.add(extractTPS(set));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -17,11 +17,15 @@
|
||||
package com.djrapitops.plan.storage.database.sql.tables;
|
||||
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Insert;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Sql;
|
||||
import com.djrapitops.plan.storage.database.sql.building.Update;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
|
||||
|
||||
@ -77,4 +81,12 @@ public class ServerTable {
|
||||
.column(MAX_PLAYERS, Sql.INT).notNull().defaultValue("-1")
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static String selectServerIds(Collection<ServerUUID> serverUUIDs) {
|
||||
return '(' + SELECT + TABLE_NAME + '.' + SERVER_ID +
|
||||
FROM + TABLE_NAME +
|
||||
WHERE + TABLE_NAME + '.' + SERVER_UUID + " IN ('" +
|
||||
new TextStringBuilder().appendWithSeparators(serverUUIDs, "','").build() +
|
||||
"'))";
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +242,9 @@ function loadservers(json, error) {
|
||||
|
||||
if (!servers || !servers.length) {
|
||||
let elements = document.getElementsByClassName('nav-servers');
|
||||
for (let i = 0; i < elements.length; i++) { elements[i].style.display = 'none'; }
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].style.display = 'none';
|
||||
}
|
||||
document.getElementById('game-server-warning').classList.remove('hidden');
|
||||
document.getElementById('data_server_list').innerHTML =
|
||||
`<div class="card shadow mb-4"><div class="card-body"><p>No servers found in the database.</p><p>It appears that Plan is not installed on any game servers or not connected to the same database. See <a href="https://github.com/plan-player-analytics/Plan/wiki">wiki</a> for Network tutorial.</p></div></div>`
|
||||
@ -445,3 +447,229 @@ function loadJoinAddressPie(json, error) {
|
||||
document.getElementById('joinAddressPie').innerText = `Failed to load graph data: ${error}`;
|
||||
}
|
||||
}
|
||||
|
||||
function loadPerformanceServerOptions() {
|
||||
const refreshElement = document.querySelector(`#performance .refresh-element`);
|
||||
refreshElement.querySelector('i').addEventListener('click', () => {
|
||||
if (refreshElement.querySelector('.refresh-notice').innerHTML.length) {
|
||||
return;
|
||||
}
|
||||
onSelectPerformanceServers();
|
||||
refreshElement.querySelector('.refresh-notice').innerHTML = '<i class="fa fa-fw fa-cog fa-spin"></i> Updating..';
|
||||
});
|
||||
const selector = document.getElementById('performance-server-selector');
|
||||
jsonRequest('./v1/network/listServers', function (json, error) {
|
||||
if (json) {
|
||||
let options = ``;
|
||||
for (let server of json.servers) {
|
||||
options += `<option${server.proxy ? ' selected' : ''} data-plan-server-uuid="${server.serverUUID}">${server.serverName}</option>`
|
||||
}
|
||||
selector.innerHTML = options;
|
||||
onSelectPerformanceServers();
|
||||
} else if (error) {
|
||||
selector.innerText = `Failed to load server list: ${error}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function onSelectPerformanceServers() {
|
||||
const selector = document.getElementById('performance-server-selector');
|
||||
const selectedServerUUIDs = [];
|
||||
|
||||
for (const option of selector.selectedOptions) {
|
||||
selectedServerUUIDs.push(option.getAttribute('data-plan-server-uuid'));
|
||||
}
|
||||
|
||||
const serverUUIDs = encodeURIComponent(JSON.stringify(selectedServerUUIDs));
|
||||
const loadedJson = {
|
||||
servers: [],
|
||||
errors: [],
|
||||
zones: {},
|
||||
colors: {},
|
||||
timestamp_f: ''
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const monthMs = 2592000000;
|
||||
const after = time - monthMs;
|
||||
for (const serverUUID of selectedServerUUIDs) {
|
||||
jsonRequest(`./v1/graph?type=optimizedPerformance&server=${serverUUID}&after=${after}`, (json, error) => {
|
||||
if (json) {
|
||||
loadedJson.servers.push(json);
|
||||
loadedJson.zones = json.zones;
|
||||
loadedJson.colors = json.colors;
|
||||
loadedJson.timestamp_f = json.timestamp_f;
|
||||
} else if (error) {
|
||||
loadedJson.errors.push(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
await awaitUntil(() => selectedServerUUIDs.length === (loadedJson.servers.length + loadedJson.errors.length));
|
||||
|
||||
jsonRequest(`./v1/network/performanceOverview?servers=${serverUUIDs}`, loadPerformanceValues);
|
||||
if (loadedJson.errors.length) {
|
||||
await loadPerformanceGraph(undefined, loadedJson.errors[0]);
|
||||
} else {
|
||||
await loadPerformanceGraph({
|
||||
servers: loadedJson.servers,
|
||||
zones: loadedJson.zones,
|
||||
colors: loadedJson.colors
|
||||
}, undefined);
|
||||
}
|
||||
const refreshElement = document.querySelector(`#performance .refresh-element`);
|
||||
refreshElement.querySelector('.refresh-time').innerText = loadedJson.timestamp_f;
|
||||
refreshElement.querySelector('.refresh-notice').innerHTML = "";
|
||||
}
|
||||
|
||||
async function loadPerformanceGraph(json, error) {
|
||||
if (json) {
|
||||
const zones = {
|
||||
tps: [{
|
||||
value: json.zones.tpsThresholdMed,
|
||||
color: json.colors.low
|
||||
}, {
|
||||
value: json.zones.tpsThresholdHigh,
|
||||
color: json.colors.med
|
||||
}, {
|
||||
value: 30,
|
||||
color: json.colors.high
|
||||
}],
|
||||
disk: [{
|
||||
value: json.zones.diskThresholdMed,
|
||||
color: json.colors.low
|
||||
}, {
|
||||
value: json.zones.diskThresholdHigh,
|
||||
color: json.colors.med
|
||||
}, {
|
||||
value: Number.MAX_VALUE,
|
||||
color: json.colors.high
|
||||
}]
|
||||
};
|
||||
const serverData = [];
|
||||
for (const server of json.servers) {
|
||||
serverData.push({
|
||||
serverName: server.serverName,
|
||||
values: await mapToDataSeries(server.values)
|
||||
});
|
||||
}
|
||||
|
||||
const series = {
|
||||
tps: [],
|
||||
cpu: [],
|
||||
ram: [],
|
||||
entities: [],
|
||||
chunks: [],
|
||||
disk: []
|
||||
}
|
||||
for (const server of serverData) {
|
||||
series.tps.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.twoDecimals,
|
||||
data: server.values.tps, color: json.colors.high, zones: zones.tps, yAxis: 0
|
||||
});
|
||||
series.cpu.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.twoDecimals,
|
||||
data: server.values.cpu, color: json.colors.cpu, yAxis: 0
|
||||
});
|
||||
series.ram.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
|
||||
data: server.values.ram, color: json.colors.ram, yAxis: 0
|
||||
});
|
||||
series.entities.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
|
||||
data: server.values.entities, color: json.colors.entities, yAxis: 0
|
||||
});
|
||||
series.chunks.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
|
||||
data: server.values.chunks, color: json.colors.chunks, yAxis: 0
|
||||
});
|
||||
series.disk.push({
|
||||
name: server.serverName, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
|
||||
data: server.values.disk, color: json.colors.high, zones: zones.disk, yAxis: 0
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => lineChart('tpsGraph', series.tps), 10);
|
||||
setTimeout(() => lineChart('cpuGraph', series.cpu), 20);
|
||||
setTimeout(() => lineChart('ramGraph', series.ram), 30);
|
||||
setTimeout(() => lineChart('entityGraph', series.entities), 40);
|
||||
setTimeout(() => lineChart('chunkGraph', series.chunks), 50);
|
||||
setTimeout(() => lineChart('diskGraph', series.disk), 60);
|
||||
} else if (error) {
|
||||
const errorMessage = `Failed to load graph data: ${error}`;
|
||||
document.getElementById('tpsGraph').innerText = errorMessage;
|
||||
document.getElementById('cpuGraph').innerText = errorMessage;
|
||||
document.getElementById('ramGraph').innerText = errorMessage;
|
||||
document.getElementById('entityGraph').innerText = errorMessage;
|
||||
document.getElementById('chunkGraph').innerText = errorMessage;
|
||||
document.getElementById('diskGraph').innerText = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This function loads Performance tab */
|
||||
function loadPerformanceValues(json, error) {
|
||||
const tab = document.getElementById('performance');
|
||||
if (error) {
|
||||
displayError(tab, error);
|
||||
return;
|
||||
}
|
||||
|
||||
// as Numbers
|
||||
let data = json.numbers;
|
||||
let element = tab.querySelector('#data_numbers');
|
||||
|
||||
element.querySelector('#data_low_tps_spikes_30d').innerText = data.low_tps_spikes_30d;
|
||||
element.querySelector('#data_low_tps_spikes_7d').innerText = data.low_tps_spikes_7d;
|
||||
element.querySelector('#data_low_tps_spikes_24h').innerText = data.low_tps_spikes_24h;
|
||||
element.querySelector('#data_server_downtime_30d').innerText = data.server_downtime_30d;
|
||||
element.querySelector('#data_server_downtime_7d').innerText = data.server_downtime_7d;
|
||||
element.querySelector('#data_server_downtime_24h').innerText = data.server_downtime_24h;
|
||||
element.querySelector('#data_avg_server_downtime_30d').innerText = data.avg_server_downtime_30d;
|
||||
element.querySelector('#data_avg_server_downtime_7d').innerText = data.avg_server_downtime_7d;
|
||||
element.querySelector('#data_avg_server_downtime_24h').innerText = data.avg_server_downtime_24h;
|
||||
element.querySelector('#data_tps_30d').innerText = data.tps_30d;
|
||||
element.querySelector('#data_tps_7d').innerText = data.tps_7d;
|
||||
element.querySelector('#data_tps_24h').innerText = data.tps_24h;
|
||||
element.querySelector('#data_cpu_30d').innerText = data.cpu_30d;
|
||||
element.querySelector('#data_cpu_7d').innerText = data.cpu_7d;
|
||||
element.querySelector('#data_cpu_24h').innerText = data.cpu_24h;
|
||||
element.querySelector('#data_ram_30d').innerText = data.ram_30d;
|
||||
element.querySelector('#data_ram_7d').innerText = data.ram_7d;
|
||||
element.querySelector('#data_ram_24h').innerText = data.ram_24h;
|
||||
element.querySelector('#data_entities_30d').innerText = data.entities_30d;
|
||||
element.querySelector('#data_entities_7d').innerText = data.entities_7d;
|
||||
element.querySelector('#data_entities_24h').innerText = data.entities_24h;
|
||||
element.querySelector('#data_chunks_30d').innerText = data.chunks_30d;
|
||||
element.querySelector('#data_chunks_7d').innerText = data.chunks_7d;
|
||||
element.querySelector('#data_chunks_24h').innerText = data.chunks_24h;
|
||||
}
|
||||
|
||||
function loadPingGraph(json, error) {
|
||||
if (json) {
|
||||
const series = {
|
||||
avgPing: {
|
||||
name: s.name.avgPing,
|
||||
type: s.type.spline,
|
||||
tooltip: s.tooltip.twoDecimals,
|
||||
data: json.avg_ping_series,
|
||||
color: json.colors.avg
|
||||
},
|
||||
maxPing: {
|
||||
name: s.name.maxPing,
|
||||
type: s.type.spline,
|
||||
tooltip: s.tooltip.zeroDecimals,
|
||||
data: json.max_ping_series,
|
||||
color: json.colors.max
|
||||
},
|
||||
minPing: {
|
||||
name: s.name.minPing,
|
||||
type: s.type.spline,
|
||||
tooltip: s.tooltip.zeroDecimals,
|
||||
data: json.min_ping_series,
|
||||
color: json.colors.min
|
||||
}
|
||||
};
|
||||
lineChart('pingGraph', [series.avgPing, series.maxPing, series.minPing]);
|
||||
} else if (error) {
|
||||
document.getElementById('pingGraph').innerText = `Failed to load graph data: ${error}`;
|
||||
}
|
||||
}
|
@ -369,7 +369,7 @@ async function loadOptimizedPerformanceGraph(json, error) {
|
||||
value: json.zones.diskThresholdMed,
|
||||
color: json.colors.low
|
||||
}, {
|
||||
value: json.zones.tpsThresholdHigh,
|
||||
value: json.zones.diskThresholdHigh,
|
||||
color: json.colors.med
|
||||
}, {
|
||||
value: Number.MAX_VALUE,
|
||||
|
@ -121,3 +121,16 @@ function newConfiguredXHR(callback) {
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function awaitUntil(predicateFunction) {
|
||||
return new Promise((resolve => {
|
||||
const handlerFunction = () => {
|
||||
if (predicateFunction.apply()) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(handlerFunction, 10)
|
||||
}
|
||||
};
|
||||
handlerFunction();
|
||||
}))
|
||||
}
|
@ -75,6 +75,9 @@
|
||||
<a class="collapse-item nav-button" href="#tab-sessions-overview"><i
|
||||
class="far fa-fw fa-calendar-alt"></i>
|
||||
Sessions</a>
|
||||
<a class="collapse-item nav-button" href="#tab-performance">
|
||||
<i class="fas fa-fw fa-cogs"></i>
|
||||
<span>Performance</span></a>
|
||||
<hr class="nav-servers dropdown-divider mx-3 my-2">
|
||||
<div class="nav-servers" id="navSrvContainer">
|
||||
</div>
|
||||
@ -471,6 +474,205 @@
|
||||
</div>
|
||||
</div> <!-- /.container-fluid -->
|
||||
</div> <!-- End of Sessions tab -->
|
||||
<!-- Begin Performance Tab -->
|
||||
<div class="tab" id="performance">
|
||||
<div class="container-fluid mt-4">
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800"><i class="sidebar-toggler fa fa-fw fa-bars"></i>${networkDisplayName}
|
||||
· Performance
|
||||
<span class="refresh-element">
|
||||
<i class="fa fa-fw fa-sync"></i> <span class="refresh-time"></span>
|
||||
<span class="refresh-notice"><i class="fa fa-fw fa-cog fa-spin"></i> Updating..</span>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Performance Charts -->
|
||||
<div class="col-xl-12 col-lg-12 col-sm-12">
|
||||
<div class="card shadow mb-4">
|
||||
<ul class="nav nav-tabs" id="performanceChartTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a aria-controls="profile" aria-selected="true"
|
||||
class="nav-link col-black active"
|
||||
data-bs-toggle="tab"
|
||||
href="#tps" id="performance-tps-tab" role="tab"><i
|
||||
class="fa fa-fw fa-tachometer-alt col-deep-orange"></i> TPS</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#cpu" id="performance-cpu-tab"
|
||||
role="tab"><i
|
||||
class="fa fa-fw fa-tachometer-alt col-amber"></i>
|
||||
CPU</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#ram" id="performance-ram-tab"
|
||||
role="tab"><i
|
||||
class="fa fa-fw fa-microchip col-light-green"></i>
|
||||
RAM</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#entity" id="performance-entity-tab"
|
||||
role="tab"><i class="fa fa-fw fa-dragon col-purple"></i>
|
||||
Entities</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#chunk" id="performance-chunk-tab"
|
||||
role="tab"><i class="fa fa-fw fa-map col-blue-grey"></i>
|
||||
Chunks</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#ping" id="performance-ping-tab" role="tab"><i
|
||||
class="fa fa-fw fa-signal col-amber"></i> Ping</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a aria-controls="contact" aria-selected="false" class="nav-link col-black"
|
||||
data-bs-toggle="tab" href="#disk" id="performance-disk-tab" role="tab"><i
|
||||
class="fa fa-fw fa-hdd col-green"></i> Disk Space</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="performanceChartTabContent">
|
||||
<div aria-labelledby="performance-tps-tab" class="tab-pane show active" id="tps"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="tpsGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-hardware-tab" class="tab-pane" id="cpu"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="cpuGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-hardware-tab" class="tab-pane" id="ram"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="ramGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-entity-tab" class="tab-pane"
|
||||
id="entity"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="entityGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-chunk-tab" class="tab-pane"
|
||||
id="chunk"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="chunkGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-ping-tab" class="tab-pane" id="ping"
|
||||
role="tabpanel">
|
||||
<p>This data is from only the proxy server (bungeecord or velocity)</p>
|
||||
<div class="chart-area" id="pingGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
<div aria-labelledby="performance-disk-tab" class="tab-pane" id="disk"
|
||||
role="tabpanel">
|
||||
<div class="chart-area" id="diskGraph"><span class="loader"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Performance as Numbers -->
|
||||
<div class="col-lg-8 mb-8 col-sm-12">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold col-black"><i
|
||||
class="fa fa-fw fa-book-open col-blue-grey"></i>
|
||||
Performance as Numbers</h6>
|
||||
</div>
|
||||
<table class="table" id="data_numbers">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th>Last 30 days</th>
|
||||
<th>Last 7 days</th>
|
||||
<th>Last 24 hours</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-exclamation-circle col-red"></i>
|
||||
Low TPS Spikes
|
||||
</td>
|
||||
<td id="data_low_tps_spikes_30d"></td>
|
||||
<td id="data_low_tps_spikes_7d"></td>
|
||||
<td id="data_low_tps_spikes_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-power-off col-red"></i>
|
||||
Total Downtime (No Data)
|
||||
</td>
|
||||
<td id="data_server_downtime_30d"></td>
|
||||
<td id="data_server_downtime_7d"></td>
|
||||
<td id="data_server_downtime_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-power-off col-red"></i>
|
||||
Average Downtime / Server
|
||||
</td>
|
||||
<td id="data_avg_server_downtime_30d"></td>
|
||||
<td id="data_avg_server_downtime_7d"></td>
|
||||
<td id="data_avg_server_downtime_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-tachometer-alt col-orange"></i> Average TPS</td>
|
||||
<td id="data_tps_30d"></td>
|
||||
<td id="data_tps_7d"></td>
|
||||
<td id="data_tps_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-tachometer-alt col-amber"></i> Average CPU Usage</td>
|
||||
<td id="data_cpu_30d"></td>
|
||||
<td id="data_cpu_7d"></td>
|
||||
<td id="data_cpu_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-microchip col-light-green"></i> Average RAM Usage</td>
|
||||
<td id="data_ram_30d"></td>
|
||||
<td id="data_ram_7d"></td>
|
||||
<td id="data_ram_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-dragon col-purple"></i> Average Entities</td>
|
||||
<td id="data_entities_30d"></td>
|
||||
<td id="data_entities_7d"></td>
|
||||
<td id="data_entities_24h"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<i class="fa fa-fw fa-map col-blue-grey"></i> Average Chunks
|
||||
<i class="far fa-fw fa-eye hidden" id="sponge-chunk-warning"
|
||||
title="Chunks unavailable on Sponge"></i>
|
||||
</td>
|
||||
<td id="data_chunks_30d"></td>
|
||||
<td id="data_chunks_7d"></td>
|
||||
<td id="data_chunks_24h"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div> <!-- End of Performance as numbers-->
|
||||
<!-- Insights -->
|
||||
<div class="col-lg-4 mb-4 col-sm-12">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold col-black"><i
|
||||
class="fa fa-fw fa-server col-light-green"></i>
|
||||
Server selector</h6>
|
||||
</div>
|
||||
<select class="form-control" id="performance-server-selector" multiple>
|
||||
<option selected>Proxy server</option>
|
||||
<option>Server 1</option>
|
||||
<option>Skyblock</option>
|
||||
<option>Server 3</option>
|
||||
</select>
|
||||
<button class="btn bg-plan" onclick="onSelectPerformanceServers()">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /.container-fluid -->
|
||||
</div> <!-- End of Performance tab -->
|
||||
<!-- Begin Playerbase Overview Tab -->
|
||||
<div class="tab" id="playerbase-overview">
|
||||
<div class="container-fluid mt-4">
|
||||
@ -928,6 +1130,7 @@
|
||||
refreshingJsonRequest("./v1/graph?type=joinAddressPie", loadJoinAddressPie, 'playerbase-overview');
|
||||
refreshingJsonRequest("./v1/graph?type=activity", loadActivityGraphs, 'playerbase-overview');
|
||||
refreshingJsonRequest("./v1/graph?type=geolocation", loadGeolocationGraph, 'geolocations');
|
||||
refreshingJsonRequest("../v1/graph?type=aggregatedPing&server=${serverUUID}", loadPingGraph, 'performance');
|
||||
|
||||
setLoadingText('Sorting out plugin tables..');
|
||||
|
||||
@ -937,6 +1140,8 @@
|
||||
responsive: true
|
||||
});
|
||||
|
||||
loadPerformanceServerOptions();
|
||||
|
||||
setLoadingText('Almost done..');
|
||||
openPage();
|
||||
setLoadingText('Done.');
|
||||
|
@ -195,6 +195,8 @@ public class AccessControlTest {
|
||||
"/v1/query,400",
|
||||
"/v1/errors,200",
|
||||
"/errors,200",
|
||||
"/v1/network/listServers,200",
|
||||
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],200",
|
||||
})
|
||||
void levelZeroCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
|
||||
int responseCode = access(resource, cookieLevel0);
|
||||
@ -255,6 +257,8 @@ public class AccessControlTest {
|
||||
"/v1/query,400",
|
||||
"/v1/errors,403",
|
||||
"/errors,403",
|
||||
"/v1/network/listServers,403",
|
||||
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
|
||||
})
|
||||
void levelOneCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
|
||||
int responseCode = access(resource, cookieLevel1);
|
||||
@ -315,6 +319,8 @@ public class AccessControlTest {
|
||||
"/v1/query,403",
|
||||
"/v1/errors,403",
|
||||
"/errors,403",
|
||||
"/v1/network/listServers,403",
|
||||
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
|
||||
})
|
||||
void levelTwoCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
|
||||
int responseCode = access(resource, cookieLevel2);
|
||||
@ -372,7 +378,9 @@ public class AccessControlTest {
|
||||
"/v1/players,403",
|
||||
"/query,403",
|
||||
"/v1/filters,403",
|
||||
"/v1/query,403"
|
||||
"/v1/query,403",
|
||||
"/v1/network/listServers,403",
|
||||
"/v1/network/performanceOverview?servers=[" + TestConstants.SERVER_UUID_STRING + "],403",
|
||||
})
|
||||
void levelHundredCanNotAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException {
|
||||
int responseCode = access(resource, cookieLevel100);
|
||||
|
Loading…
Reference in New Issue
Block a user