Started work on network health tab #601

This commit is contained in:
Rsl1122 2018-07-11 20:50:22 +03:00
parent 46fa30d224
commit b6fc279649
18 changed files with 609 additions and 306 deletions

View File

@ -9,6 +9,7 @@ import com.djrapitops.plan.data.store.keys.ServerKeys;
import com.djrapitops.plan.data.store.mutators.*;
import com.djrapitops.plan.data.store.mutators.combiners.MultiBanCombiner;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.data.store.mutators.health.HealthInformation;
import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.ServerInfo;

View File

@ -8,13 +8,16 @@ import com.djrapitops.plan.data.store.keys.ServerKeys;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.TPSMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.data.store.mutators.health.NetworkHealthInformation;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.theme.Theme;
import com.djrapitops.plan.system.settings.theme.ThemeVal;
import com.djrapitops.plan.utilities.MiscUtils;
import com.djrapitops.plan.utilities.html.graphs.ActivityStackGraph;
import com.djrapitops.plan.utilities.html.graphs.WorldMap;
import com.djrapitops.plan.utilities.html.graphs.line.OnlineActivityGraph;
import com.djrapitops.plan.utilities.html.graphs.pie.ActivityPie;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.api.utility.log.Log;
@ -44,6 +47,14 @@ public class NetworkContainer extends DataContainer {
addConstants();
addPlayerInformation();
addNetworkHealth();
}
private void addNetworkHealth() {
Key<NetworkHealthInformation> healthInformation = new Key<>(NetworkHealthInformation.class, "HEALTH_INFORMATION");
putSupplier(healthInformation, () -> new NetworkHealthInformation(this));
putSupplier(NetworkKeys.HEALTH_INDEX, () -> getUnsafe(healthInformation).getServerHealth());
putSupplier(NetworkKeys.HEALTH_NOTES, () -> getUnsafe(healthInformation).toHtml());
}
public void putAnalysisContainer(AnalysisContainer analysisContainer) {
@ -91,6 +102,15 @@ public class NetworkContainer extends DataContainer {
putSupplier(NetworkKeys.PLAYERS_ONLINE_SERIES, () ->
new OnlineActivityGraph(TPSMutator.forContainer(bungeeContainer)).toHighChartsSeries()
);
Key<ActivityStackGraph> activityStackGraph = new Key<>(ActivityStackGraph.class, "ACTIVITY_STACK_GRAPH");
putSupplier(NetworkKeys.ACTIVITY_DATA, () -> getUnsafe(NetworkKeys.PLAYERS_MUTATOR).toActivityDataMap(getUnsafe(NetworkKeys.REFRESH_TIME)));
putSupplier(activityStackGraph, () -> new ActivityStackGraph(getUnsafe(NetworkKeys.ACTIVITY_DATA)));
putSupplier(NetworkKeys.ACTIVITY_STACK_CATEGORIES, () -> getUnsafe(activityStackGraph).toHighChartsLabels());
putSupplier(NetworkKeys.ACTIVITY_STACK_SERIES, () -> getUnsafe(activityStackGraph).toHighChartsSeries());
putSupplier(NetworkKeys.ACTIVITY_PIE_SERIES, () ->
new ActivityPie(getUnsafe(NetworkKeys.ACTIVITY_DATA).get(getUnsafe(NetworkKeys.REFRESH_TIME))).toHighChartsSeries()
);
putSupplier(NetworkKeys.ALL_TIME_PEAK_TIME_F, () ->
bungeeContainer.getValue(ServerKeys.ALL_TIME_PEAK_PLAYERS).map(Formatters.year()::apply).orElse("No data")
);

View File

@ -50,7 +50,7 @@ public class AnalysisKeys {
public static final PlaceholderKey<String> SESSION_TABLE = new PlaceholderKey<>(String.class, "tableBodySessions");
public static final PlaceholderKey<String> RECENT_LOGINS = new PlaceholderKey<>(String.class, "listRecentLogins");
public static final PlaceholderKey<String> COMMAND_USAGE_TABLE = new PlaceholderKey<>(String.class, "tableCommandUsage");
public static final PlaceholderKey<String> HEALTH_NOTES = new PlaceholderKey<>(String.class, "healthNotes");
public static final PlaceholderKey<String> HEALTH_NOTES = CommonPlaceholderKeys.HEALTH_NOTES;
public static final PlaceholderKey<String> PLUGINS_TAB = new PlaceholderKey<>(String.class, "tabsPlugins");
public static final PlaceholderKey<String> PLUGINS_TAB_NAV = new PlaceholderKey<>(String.class, "navPluginsTabs");
// Formatted time values
@ -69,7 +69,7 @@ public class AnalysisKeys {
public static final PlaceholderKey<Integer> DEATHS = new PlaceholderKey<>(Integer.class, "deaths");
public static final PlaceholderKey<Integer> MOB_KILL_COUNT = new PlaceholderKey<>(Integer.class, "mobKillCount");
public static final PlaceholderKey<Integer> PLAYER_KILL_COUNT = new PlaceholderKey<>(Integer.class, "killCount");
public static final PlaceholderKey<Double> HEALTH_INDEX = new PlaceholderKey<>(Double.class, "healthIndex");
public static final PlaceholderKey<Double> HEALTH_INDEX = CommonPlaceholderKeys.HEALTH_INDEX;
public static final PlaceholderKey<Integer> COMMAND_COUNT = new PlaceholderKey<>(Integer.class, "commandCount");
public static final PlaceholderKey<Integer> COMMAND_COUNT_UNIQUE = new PlaceholderKey<>(Integer.class, "commandUniqueCount");
//
@ -123,9 +123,9 @@ public class AnalysisKeys {
public static final PlaceholderKey<String> CHUNK_SERIES = new PlaceholderKey<>(String.class, "chunkSeries");
public static final PlaceholderKey<String> PUNCHCARD_SERIES = new PlaceholderKey<>(String.class, "punchCardSeries");
public static final PlaceholderKey<String> WORLD_MAP_SERIES = CommonPlaceholderKeys.WORLD_MAP_SERIES;
public static final PlaceholderKey<String> ACTIVITY_STACK_SERIES = new PlaceholderKey<>(String.class, "activityStackSeries");
public static final PlaceholderKey<String> ACTIVITY_STACK_CATEGORIES = new PlaceholderKey<>(String.class, "activityStackCategories");
public static final PlaceholderKey<String> ACTIVITY_PIE_SERIES = new PlaceholderKey<>(String.class, "activityPieSeries");
public static final PlaceholderKey<String> ACTIVITY_STACK_SERIES = CommonPlaceholderKeys.ACTIVITY_STACK_SERIES;
public static final PlaceholderKey<String> ACTIVITY_STACK_CATEGORIES = CommonPlaceholderKeys.ACTIVITY_STACK_CATEGORIES;
public static final PlaceholderKey<String> ACTIVITY_PIE_SERIES = CommonPlaceholderKeys.ACTIVITY_PIE_SERIES;
public static final PlaceholderKey<String> CALENDAR_SERIES = new PlaceholderKey<>(String.class, "calendarSeries");
// Variables used only during analysis
public static final Key<SessionsMutator> SESSIONS_MUTATOR = CommonKeys.SESSIONS_MUTATOR;
@ -138,7 +138,7 @@ public class AnalysisKeys {
public static final Key<Long> ANALYSIS_TIME_WEEK_AGO = new Key<>(Long.class, "ANALYSIS_TIME_WEEK_AGO");
public static final Key<Long> ANALYSIS_TIME_MONTH_AGO = new Key<>(Long.class, "ANALYSIS_TIME_MONTH_AGO");
public static final Key<Map<UUID, String>> PLAYER_NAMES = new Key<>(new Type<Map<UUID, String>>() {}, "PLAYER_NAMES");
public static final Key<TreeMap<Long, Map<String, Set<UUID>>>> ACTIVITY_DATA = new Key<>(new Type<TreeMap<Long, Map<String, Set<UUID>>>>() {}, "ACTIVITY_DATA");
public static final Key<TreeMap<Long, Map<String, Set<UUID>>>> ACTIVITY_DATA = CommonKeys.ACTIVITY_DATA;
public static final Key<Set<UUID>> BAN_DATA = new Key<>(new Type<Set<UUID>>() {}, "BAN_DATA");
private AnalysisKeys() {

View File

@ -11,8 +11,7 @@ import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.TPSMutator;
import com.djrapitops.plan.data.time.WorldTimes;
import java.util.List;
import java.util.UUID;
import java.util.*;
/**
* Class holding Key objects that are commonly used across multiple DataContainers.
@ -47,4 +46,6 @@ public class CommonKeys {
public static final Key<TPSMutator> TPS_MUTATOR = new Key<>(TPSMutator.class, "TPS_MUTATOR");
public static final Key<PlayersMutator> PLAYERS_MUTATOR = new Key<>(PlayersMutator.class, "PLAYERS_MUTATOR");
public static final Key<TreeMap<Long, Map<String, Set<UUID>>>> ACTIVITY_DATA = new Key<>(new Type<TreeMap<Long, Map<String, Set<UUID>>>>() {}, "ACTIVITY_DATA");
}

View File

@ -19,6 +19,12 @@ class CommonPlaceholderKeys {
static final PlaceholderKey<Integer> PLAYERS_ONLINE = new PlaceholderKey<>(Integer.class, "playersOnline");
static final PlaceholderKey<Integer> PLAYERS_TOTAL = new PlaceholderKey<>(Integer.class, "playersTotal");
static final PlaceholderKey<String> WORLD_MAP_SERIES = new PlaceholderKey<>(String.class, "geoMapSeries");
static final PlaceholderKey<String> ACTIVITY_STACK_SERIES = new PlaceholderKey<>(String.class, "activityStackSeries");
static final PlaceholderKey<String> ACTIVITY_STACK_CATEGORIES = new PlaceholderKey<>(String.class, "activityStackCategories");
static final PlaceholderKey<String> ACTIVITY_PIE_SERIES = new PlaceholderKey<>(String.class, "activityPieSeries");
static final PlaceholderKey<String> HEALTH_NOTES = new PlaceholderKey<>(String.class, "healthNotes");
static final PlaceholderKey<Double> HEALTH_INDEX = new PlaceholderKey<>(Double.class, "healthIndex");
static final PlaceholderKey<Integer> PLAYERS_DAY = new PlaceholderKey<>(Integer.class, "playersDay");
static final PlaceholderKey<Integer> PLAYERS_WEEK = new PlaceholderKey<>(Integer.class, "playersWeek");

View File

@ -4,6 +4,11 @@ import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.PlaceholderKey;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
/**
* Key objects for {@link com.djrapitops.plan.data.store.containers.NetworkContainer}.
*
@ -35,6 +40,11 @@ public class NetworkKeys {
public static final PlaceholderKey<String> WORLD_MAP_SERIES = CommonPlaceholderKeys.WORLD_MAP_SERIES;
public static final PlaceholderKey<String> PLAYERS_ONLINE_SERIES = CommonPlaceholderKeys.PLAYERS_ONLINE_SERIES;
public static final PlaceholderKey<String> ACTIVITY_STACK_SERIES = CommonPlaceholderKeys.ACTIVITY_STACK_SERIES;
public static final PlaceholderKey<String> ACTIVITY_STACK_CATEGORIES = CommonPlaceholderKeys.ACTIVITY_STACK_CATEGORIES;
public static final PlaceholderKey<String> ACTIVITY_PIE_SERIES = CommonPlaceholderKeys.ACTIVITY_PIE_SERIES;
public static final PlaceholderKey<Double> HEALTH_INDEX = CommonPlaceholderKeys.HEALTH_INDEX;
public static final PlaceholderKey<String> HEALTH_NOTES = CommonPlaceholderKeys.HEALTH_NOTES;
public static final Key<Long> REFRESH_TIME = new Key<>(Long.class, "REFRESH_TIME");
public static final Key<Long> REFRESH_TIME_DAY_AGO = new Key<>(Long.class, "REFRESH_TIME_DAY_AGO");
@ -42,6 +52,8 @@ public class NetworkKeys {
public static final Key<Long> REFRESH_TIME_MONTH_AGO = new Key<>(Long.class, "REFRESH_TIME_MONTH_AGO");
public static final Key<PlayersMutator> PLAYERS_MUTATOR = CommonKeys.PLAYERS_MUTATOR;
public static final Key<TreeMap<Long, Map<String, Set<UUID>>>> ACTIVITY_DATA = CommonKeys.ACTIVITY_DATA;
private NetworkKeys() {
/* static variable class */
}

View File

@ -1,259 +0,0 @@
/*
* License is provided in the jar as LICENSE also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE
*/
package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.containers.AnalysisContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.html.Html;
import com.djrapitops.plugin.api.TimeAmount;
import java.util.*;
/**
* Server Health analysis mutator.
*
* @author Rsl1122
*/
public class HealthInformation {
private final AnalysisContainer analysisContainer;
private final List<String> notes;
private final long now;
private double serverHealth;
private long fourWeeksAgo;
public HealthInformation(AnalysisContainer analysisContainer) {
this.analysisContainer = analysisContainer;
this.notes = new ArrayList<>();
now = analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME);
fourWeeksAgo = analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO);
serverHealth = 100.0;
calculate();
}
public String toHtml() {
StringBuilder healthNoteBuilder = new StringBuilder();
for (String healthNote : notes) {
healthNoteBuilder.append(healthNote);
}
return healthNoteBuilder.toString();
}
private void calculate() {
activityChangeNote();
newPlayerNote();
activePlayerPlaytimeChange();
lowPerformance();
}
public double getServerHealth() {
return serverHealth;
}
private void activityChangeNote() {
TreeMap<Long, Map<String, Set<UUID>>> activityData = analysisContainer.getUnsafe(AnalysisKeys.ACTIVITY_DATA);
Map<String, Set<UUID>> activityNow = activityData.getOrDefault(now, new HashMap<>());
Set<UUID> veryActiveNow = activityNow.getOrDefault("Very Active", new HashSet<>());
Set<UUID> activeNow = activityNow.getOrDefault("Active", new HashSet<>());
Set<UUID> regularNow = activityNow.getOrDefault("Regular", new HashSet<>());
Map<String, Set<UUID>> activityFourWAgo = activityData.getOrDefault(fourWeeksAgo, new HashMap<>());
Set<UUID> veryActiveFWAG = activityFourWAgo.getOrDefault("Very Active", new HashSet<>());
Set<UUID> activeFWAG = activityFourWAgo.getOrDefault("Active", new HashSet<>());
Set<UUID> regularFWAG = activityFourWAgo.getOrDefault("Regular", new HashSet<>());
Set<UUID> regularRemainCompareSet = new HashSet<>(regularFWAG);
regularRemainCompareSet.addAll(activeFWAG);
regularRemainCompareSet.addAll(veryActiveFWAG);
int activeFWAGNum = regularRemainCompareSet.size();
regularRemainCompareSet.removeAll(regularNow);
regularRemainCompareSet.removeAll(activeNow);
regularRemainCompareSet.removeAll(veryActiveNow);
int notRegularAnymore = regularRemainCompareSet.size();
int remain = activeFWAGNum - notRegularAnymore;
double percRemain = remain * 100.0 / activeFWAGNum;
int newActive = getNewActive(veryActiveNow, activeNow, regularNow, veryActiveFWAG, activeFWAG, regularFWAG);
int change = newActive - notRegularAnymore;
String remainNote = "";
if (activeFWAGNum != 0) {
remainNote = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
if (percRemain > 50) {
remainNote += Html.GREEN_THUMB.parse();
} else if (percRemain > 20) {
remainNote += Html.YELLOW_FLAG.parse();
} else {
remainNote += Html.RED_WARN.parse();
serverHealth -= 2.5;
}
remainNote += " " + FormatUtils.cutDecimals(percRemain) + "% of regular players have remained active ("
+ remain + "/" + activeFWAGNum + ")";
}
if (change > 0) {
notes.add(
"<p>" + Html.GREEN_THUMB.parse() + " Number of regular players has increased (+" + change + ")<br>" +
remainNote + "</p>");
} else if (change == 0) {
notes.add(
"<p>" + Html.GREEN_THUMB.parse() + " Number of regular players has stayed the same (+" + change + ")<br>" +
remainNote + "</p>");
} else if (change > -20) {
notes.add(
"<p>" + Html.YELLOW_FLAG.parse() + " Number of regular players has decreased (" + change + ")<br>" +
remainNote + "</p>");
serverHealth -= 5;
} else {
notes.add(
"<p>" + Html.RED_WARN.parse() + " Number of regular players has decreased (" + change + ")<br>" +
remainNote + "</p>");
serverHealth -= 10;
}
}
private void newPlayerNote() {
Key<PlayersMutator> newMonth = new Key<>(PlayersMutator.class, "NEW_MONTH");
PlayersMutator newPlayersMonth = analysisContainer.getValue(newMonth).orElse(new PlayersMutator(new ArrayList<>()));
PlayersOnlineResolver onlineResolver = analysisContainer.getUnsafe(AnalysisKeys.PLAYERS_ONLINE_RESOLVER);
double avgOnlineOnRegister = newPlayersMonth.registerDates().stream()
.mapToInt(date -> onlineResolver.getOnlineOn(date).orElse(-1))
.filter(value -> value != -1)
.average().orElse(0);
if (avgOnlineOnRegister >= 1) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " New Players have players to play with when they join ("
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " New Players may not have players to play with when they join ("
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");
serverHealth -= 5;
}
long playersNewMonth = analysisContainer.getValue(AnalysisKeys.PLAYERS_NEW_MONTH).orElse(0);
long playersRetainedMonth = analysisContainer.getValue(AnalysisKeys.PLAYERS_RETAINED_MONTH).orElse(0);
if (playersNewMonth != 0) {
double retainPercentage = playersRetainedMonth / playersNewMonth;
if (retainPercentage >= 0.25) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " " + Formatters.percentage().apply(retainPercentage)
+ " of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " " + Formatters.percentage().apply(retainPercentage)
+ "% of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
}
}
}
private void activePlayerPlaytimeChange() {
PlayersMutator currentlyActive = analysisContainer.getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).filterActive(now, 1.75);
long twoWeeksAgo = (now - (now - fourWeeksAgo)) / 2L;
long totalFourToTwoWeeks = 0;
long totalLastTwoWeeks = 0;
for (PlayerContainer activePlayer : currentlyActive.all()) {
totalFourToTwoWeeks += SessionsMutator.forContainer(activePlayer)
.filterSessionsBetween(fourWeeksAgo, twoWeeksAgo).toActivePlaytime();
totalLastTwoWeeks += SessionsMutator.forContainer(activePlayer)
.filterSessionsBetween(twoWeeksAgo, now).toActivePlaytime();
}
int activeCount = currentlyActive.count();
if (activeCount != 0) {
long avgFourToTwoWeeks = totalFourToTwoWeeks / (long) activeCount;
long avgLastTwoWeeks = totalLastTwoWeeks / (long) activeCount;
String avgLastTwoWeeksString = Formatters.timeAmount().apply(avgLastTwoWeeks);
String avgFourToTwoWeeksString = Formatters.timeAmount().apply(avgFourToTwoWeeks);
if (avgFourToTwoWeeks >= avgLastTwoWeeks) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " Active players seem to have things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
} else if (avgFourToTwoWeeks - avgLastTwoWeeks > TimeAmount.HOUR.ms() * 2L) {
notes.add("<p>" + Html.RED_WARN.parse() + " Active players might be running out of things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
serverHealth -= 5;
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " Active players might be running out of things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
}
}
}
private void lowPerformance() {
Key<TPSMutator> tpsMonth = new Key<>(TPSMutator.class, "TPS_MONTH");
TPSMutator tpsMutator = analysisContainer.getUnsafe(tpsMonth);
long serverDownTime = tpsMutator.serverDownTime();
double aboveThreshold = tpsMutator.percentageTPSAboveLowThreshold();
long tpsSpikeMonth = analysisContainer.getValue(AnalysisKeys.TPS_SPIKE_MONTH).orElse(0);
String avgLowThresholdString = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
if (aboveThreshold >= 0.96) {
avgLowThresholdString += Html.GREEN_THUMB.parse();
} else if (aboveThreshold >= 0.9) {
avgLowThresholdString += Html.YELLOW_FLAG.parse();
serverHealth *= 0.9;
} else {
avgLowThresholdString += Html.RED_WARN.parse();
serverHealth *= 0.6;
}
avgLowThresholdString += " Average TPS was above Low Threshold "
+ FormatUtils.cutDecimals(aboveThreshold * 100.0) + "% of the time";
if (tpsSpikeMonth <= 5) {
notes.add("<p>" + Html.GREEN_THUMB.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
} else if (tpsSpikeMonth <= 25) {
notes.add("<p>" + Html.YELLOW_FLAG.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
serverHealth *= 0.95;
} else {
notes.add("<p>" + Html.RED_WARN.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
serverHealth *= 0.8;
}
Formatter<Long> formatter = Formatters.timeAmount();
if (serverDownTime <= TimeAmount.DAY.ms()) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
} else if (serverDownTime <= TimeAmount.WEEK.ms()) {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
serverHealth *= (TimeAmount.WEEK.ms() - serverDownTime) * 1.0 / TimeAmount.WEEK.ms();
} else {
notes.add("<p>" + Html.RED_WARN.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
serverHealth *= (TimeAmount.MONTH.ms() - serverDownTime) * 1.0 / TimeAmount.MONTH.ms();
}
}
private int getNewActive(Set<UUID> veryActiveNow, Set<UUID> activeNow, Set<UUID> regularNow, Set<UUID> veryActiveFWAG, Set<UUID> activeFWAG, Set<UUID> regularFWAG) {
Set<UUID> regularNewCompareSet = new HashSet<>(regularNow);
regularNewCompareSet.addAll(activeNow);
regularNewCompareSet.addAll(veryActiveNow);
regularNewCompareSet.removeAll(regularFWAG);
regularNewCompareSet.removeAll(activeFWAG);
regularNewCompareSet.removeAll(veryActiveFWAG);
return regularNewCompareSet.size();
}
}

View File

@ -0,0 +1,48 @@
package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.PerServerContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.CommonKeys;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import java.util.*;
public class NetworkPerServerMutator {
private final Map<UUID, List<DataContainer>> perServerContainers;
public NetworkPerServerMutator(PlayersMutator playersMutator) {
this.perServerContainers = perServerContainers(playersMutator);
}
public static NetworkPerServerMutator forContainer(DataContainer container) {
return new NetworkPerServerMutator(
container.getValue(CommonKeys.PLAYERS_MUTATOR)
.orElse(PlayersMutator.forContainer(container))
);
}
public Map<UUID, List<DataContainer>> getPerServerContainers() {
return perServerContainers;
}
private Map<UUID, List<DataContainer>> perServerContainers(PlayersMutator playersMutator) {
Map<UUID, List<DataContainer>> dataContainerMap = new HashMap<>();
for (PlayerContainer playerContainer : playersMutator.all()) {
UUID uuid = playerContainer.getUnsafe(PlayerKeys.UUID);
PerServerContainer perServerContainer = playerContainer.getValue(PlayerKeys.PER_SERVER).orElse(new PerServerContainer());
for (Map.Entry<UUID, DataContainer> entry : perServerContainer.entrySet()) {
UUID serverUUID = entry.getKey();
DataContainer container = entry.getValue();
container.putRawData(PlayerKeys.UUID, uuid);
List<DataContainer> dataContainers = dataContainerMap.getOrDefault(serverUUID, new ArrayList<>());
dataContainers.add(container);
dataContainerMap.put(serverUUID, dataContainers);
}
}
return dataContainerMap;
}
}

View File

@ -15,16 +15,16 @@ import java.util.stream.Collectors;
*
* @author Rsl1122
*/
public class PerServerDataMutator {
public class PerServerMutator {
private final PerServerContainer data;
public PerServerDataMutator(PerServerContainer data) {
public PerServerMutator(PerServerContainer data) {
this.data = data;
}
public static PerServerDataMutator forContainer(DataContainer container) {
return new PerServerDataMutator(container.getValue(PlayerKeys.PER_SERVER).orElse(new PerServerContainer()));
public static PerServerMutator forContainer(DataContainer container) {
return new PerServerMutator(container.getValue(PlayerKeys.PER_SERVER).orElse(new PerServerContainer()));
}
public List<Session> flatMapSessions() {

View File

@ -107,7 +107,7 @@ public class PlayersMutator {
Map<String, Set<UUID>> map = activityData.getOrDefault(time, new HashMap<>());
if (!players.isEmpty()) {
for (PlayerContainer player : players) {
if (player.getValue(PlayerKeys.REGISTERED).orElse(0L) < time) {
if (player.getValue(PlayerKeys.REGISTERED).orElse(0L) > time) {
continue;
}
ActivityIndex activityIndex = player.getActivityIndex(time);

View File

@ -0,0 +1,150 @@
package com.djrapitops.plan.data.store.mutators.health;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.html.Html;
import com.djrapitops.plugin.api.TimeAmount;
import java.util.*;
public abstract class AbstractHealthInfo {
protected final List<String> notes;
protected final long now;
protected final long monthAgo;
protected double serverHealth;
public AbstractHealthInfo(long now, long monthAgo) {
this.now = now;
this.monthAgo = monthAgo;
serverHealth = 100.0;
this.notes = new ArrayList<>();
}
protected abstract void calculate();
public double getServerHealth() {
return serverHealth;
}
public String toHtml() {
StringBuilder healthNoteBuilder = new StringBuilder();
for (String healthNote : notes) {
healthNoteBuilder.append(healthNote);
}
return healthNoteBuilder.toString();
}
protected void activityChangeNote(TreeMap<Long, Map<String, Set<UUID>>> activityData) {
Map<String, Set<UUID>> activityNow = activityData.getOrDefault(now, new HashMap<>());
Set<UUID> veryActiveNow = activityNow.getOrDefault("Very Active", new HashSet<>());
Set<UUID> activeNow = activityNow.getOrDefault("Active", new HashSet<>());
Set<UUID> regularNow = activityNow.getOrDefault("Regular", new HashSet<>());
Map<String, Set<UUID>> activityFourWAgo = activityData.getOrDefault(monthAgo, new HashMap<>());
Set<UUID> veryActiveFWAG = activityFourWAgo.getOrDefault("Very Active", new HashSet<>());
Set<UUID> activeFWAG = activityFourWAgo.getOrDefault("Active", new HashSet<>());
Set<UUID> regularFWAG = activityFourWAgo.getOrDefault("Regular", new HashSet<>());
Set<UUID> regularRemainCompareSet = new HashSet<>(regularFWAG);
regularRemainCompareSet.addAll(activeFWAG);
regularRemainCompareSet.addAll(veryActiveFWAG);
int activeFWAGNum = regularRemainCompareSet.size();
regularRemainCompareSet.removeAll(regularNow);
regularRemainCompareSet.removeAll(activeNow);
regularRemainCompareSet.removeAll(veryActiveNow);
int notRegularAnymore = regularRemainCompareSet.size();
int remain = activeFWAGNum - notRegularAnymore;
double percRemain = remain * 100.0 / activeFWAGNum;
int newActive = getNewActive(veryActiveNow, activeNow, regularNow, veryActiveFWAG, activeFWAG, regularFWAG);
int change = newActive - notRegularAnymore;
String remainNote = "";
if (activeFWAGNum != 0) {
remainNote = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
if (percRemain > 50) {
remainNote += Html.GREEN_THUMB.parse();
} else if (percRemain > 20) {
remainNote += Html.YELLOW_FLAG.parse();
} else {
remainNote += Html.RED_WARN.parse();
serverHealth -= 2.5;
}
remainNote += " " + FormatUtils.cutDecimals(percRemain) + "% of regular players have remained active ("
+ remain + "/" + activeFWAGNum + ")";
}
if (change > 0) {
notes.add(
"<p>" + Html.GREEN_THUMB.parse() + " Number of regular players has increased (+" + change + ")<br>" +
remainNote + "</p>");
} else if (change == 0) {
notes.add(
"<p>" + Html.GREEN_THUMB.parse() + " Number of regular players has stayed the same (+" + change + ")<br>" +
remainNote + "</p>");
} else if (change > -20) {
notes.add(
"<p>" + Html.YELLOW_FLAG.parse() + " Number of regular players has decreased (" + change + ")<br>" +
remainNote + "</p>");
serverHealth -= 5;
} else {
notes.add(
"<p>" + Html.RED_WARN.parse() + " Number of regular players has decreased (" + change + ")<br>" +
remainNote + "</p>");
serverHealth -= 10;
}
}
protected void activePlayerPlaytimeChange(PlayersMutator playersMutator) {
PlayersMutator currentlyActive = playersMutator.filterActive(now, 1.75);
long twoWeeksAgo = (now - (now - monthAgo)) / 2L;
long totalFourToTwoWeeks = 0;
long totalLastTwoWeeks = 0;
for (PlayerContainer activePlayer : currentlyActive.all()) {
totalFourToTwoWeeks += SessionsMutator.forContainer(activePlayer)
.filterSessionsBetween(monthAgo, twoWeeksAgo).toActivePlaytime();
totalLastTwoWeeks += SessionsMutator.forContainer(activePlayer)
.filterSessionsBetween(twoWeeksAgo, now).toActivePlaytime();
}
int activeCount = currentlyActive.count();
if (activeCount != 0) {
long avgFourToTwoWeeks = totalFourToTwoWeeks / (long) activeCount;
long avgLastTwoWeeks = totalLastTwoWeeks / (long) activeCount;
String avgLastTwoWeeksString = Formatters.timeAmount().apply(avgLastTwoWeeks);
String avgFourToTwoWeeksString = Formatters.timeAmount().apply(avgFourToTwoWeeks);
if (avgFourToTwoWeeks >= avgLastTwoWeeks) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " Active players seem to have things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
} else if (avgFourToTwoWeeks - avgLastTwoWeeks > TimeAmount.HOUR.ms() * 2L) {
notes.add("<p>" + Html.RED_WARN.parse() + " Active players might be running out of things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
serverHealth -= 5;
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " Active players might be running out of things to do (Played "
+ avgLastTwoWeeksString + " vs " + avgFourToTwoWeeksString
+ ", last two weeks vs weeks 2-4)</p>");
}
}
}
private int getNewActive(Set<UUID> veryActiveNow, Set<UUID> activeNow, Set<UUID> regularNow, Set<UUID> veryActiveFWAG, Set<UUID> activeFWAG, Set<UUID> regularFWAG) {
Set<UUID> regularNewCompareSet = new HashSet<>(regularNow);
regularNewCompareSet.addAll(activeNow);
regularNewCompareSet.addAll(veryActiveNow);
regularNewCompareSet.removeAll(regularFWAG);
regularNewCompareSet.removeAll(activeFWAG);
regularNewCompareSet.removeAll(veryActiveFWAG);
return regularNewCompareSet.size();
}
}

View File

@ -0,0 +1,143 @@
/*
* License is provided in the jar as LICENSE also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/LICENSE
*/
package com.djrapitops.plan.data.store.mutators.health;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.containers.AnalysisContainer;
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.PlayersOnlineResolver;
import com.djrapitops.plan.data.store.mutators.TPSMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.html.Html;
import com.djrapitops.plugin.api.TimeAmount;
import java.util.ArrayList;
/**
* Server Health analysis mutator.
*
* @author Rsl1122
*/
public class HealthInformation extends AbstractHealthInfo {
private final AnalysisContainer analysisContainer;
public HealthInformation(AnalysisContainer analysisContainer) {
super(
analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME),
analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO)
);
this.analysisContainer = analysisContainer;
calculate();
}
public String toHtml() {
StringBuilder healthNoteBuilder = new StringBuilder();
for (String healthNote : notes) {
healthNoteBuilder.append(healthNote);
}
return healthNoteBuilder.toString();
}
@Override
protected void calculate() {
activityChangeNote(analysisContainer.getUnsafe(AnalysisKeys.ACTIVITY_DATA));
newPlayerNote();
activePlayerPlaytimeChange(analysisContainer.getUnsafe(AnalysisKeys.PLAYERS_MUTATOR));
lowPerformance();
}
private void newPlayerNote() {
Key<PlayersMutator> newMonth = new Key<>(PlayersMutator.class, "NEW_MONTH");
PlayersMutator newPlayersMonth = analysisContainer.getValue(newMonth).orElse(new PlayersMutator(new ArrayList<>()));
PlayersOnlineResolver onlineResolver = analysisContainer.getUnsafe(AnalysisKeys.PLAYERS_ONLINE_RESOLVER);
double avgOnlineOnRegister = newPlayersMonth.registerDates().stream()
.mapToInt(date -> onlineResolver.getOnlineOn(date).orElse(-1))
.filter(value -> value != -1)
.average().orElse(0);
if (avgOnlineOnRegister >= 1) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " New Players have players to play with when they join ("
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " New Players may not have players to play with when they join ("
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");
serverHealth -= 5;
}
long playersNewMonth = analysisContainer.getValue(AnalysisKeys.PLAYERS_NEW_MONTH).orElse(0);
long playersRetainedMonth = analysisContainer.getValue(AnalysisKeys.PLAYERS_RETAINED_MONTH).orElse(0);
if (playersNewMonth != 0) {
double retainPercentage = playersRetainedMonth / playersNewMonth;
if (retainPercentage >= 0.25) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " " + Formatters.percentage().apply(retainPercentage)
+ " of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
} else {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " " + Formatters.percentage().apply(retainPercentage)
+ "% of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
}
}
}
private void lowPerformance() {
Key<TPSMutator> tpsMonth = new Key<>(TPSMutator.class, "TPS_MONTH");
TPSMutator tpsMutator = analysisContainer.getUnsafe(tpsMonth);
long serverDownTime = tpsMutator.serverDownTime();
double aboveThreshold = tpsMutator.percentageTPSAboveLowThreshold();
long tpsSpikeMonth = analysisContainer.getValue(AnalysisKeys.TPS_SPIKE_MONTH).orElse(0);
String avgLowThresholdString = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
if (aboveThreshold >= 0.96) {
avgLowThresholdString += Html.GREEN_THUMB.parse();
} else if (aboveThreshold >= 0.9) {
avgLowThresholdString += Html.YELLOW_FLAG.parse();
serverHealth *= 0.9;
} else {
avgLowThresholdString += Html.RED_WARN.parse();
serverHealth *= 0.6;
}
avgLowThresholdString += " Average TPS was above Low Threshold "
+ FormatUtils.cutDecimals(aboveThreshold * 100.0) + "% of the time";
if (tpsSpikeMonth <= 5) {
notes.add("<p>" + Html.GREEN_THUMB.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
} else if (tpsSpikeMonth <= 25) {
notes.add("<p>" + Html.YELLOW_FLAG.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
serverHealth *= 0.95;
} else {
notes.add("<p>" + Html.RED_WARN.parse()
+ " Average TPS dropped below Low Threshold (" + Settings.THEME_GRAPH_TPS_THRESHOLD_MED.getNumber() + ")" +
" " + tpsSpikeMonth + " times<br>" +
avgLowThresholdString + "</p>");
serverHealth *= 0.8;
}
Formatter<Long> formatter = Formatters.timeAmount();
if (serverDownTime <= TimeAmount.DAY.ms()) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
} else if (serverDownTime <= TimeAmount.WEEK.ms()) {
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
serverHealth *= (TimeAmount.WEEK.ms() - serverDownTime) * 1.0 / TimeAmount.WEEK.ms();
} else {
notes.add("<p>" + Html.RED_WARN.parse() + " Total Server downtime (No Data) was "
+ formatter.apply(serverDownTime) + "</p>");
serverHealth *= (TimeAmount.MONTH.ms() - serverDownTime) * 1.0 / TimeAmount.MONTH.ms();
}
}
}

View File

@ -0,0 +1,31 @@
package com.djrapitops.plan.data.store.mutators.health;
import com.djrapitops.plan.data.store.containers.NetworkContainer;
import com.djrapitops.plan.data.store.keys.NetworkKeys;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
public class NetworkHealthInformation extends AbstractHealthInfo {
private final NetworkContainer container;
public NetworkHealthInformation(NetworkContainer container) {
super(
container.getUnsafe(NetworkKeys.REFRESH_TIME),
container.getUnsafe(NetworkKeys.REFRESH_TIME_MONTH_AGO)
);
this.container = container;
calculate();
}
@Override
protected void calculate() {
activityChangeNote(container.getUnsafe(NetworkKeys.ACTIVITY_DATA));
activePlayerPlaytimeChange(container.getUnsafe(NetworkKeys.PLAYERS_MUTATOR));
perServerComparisonNote(container.getUnsafe(NetworkKeys.PLAYERS_MUTATOR));
}
private void perServerComparisonNote(PlayersMutator playersMutator) {
}
}

View File

@ -18,11 +18,11 @@ public interface FetchOperations {
* Used to get a NetworkContainer, some limitations apply to values returned by DataContainer keys.
* <p>
* Limitations:
* - Bungee ServerContainer does not support: ServerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_KILL_COUNT
* - Bungee ServerContainer does not support: ServerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_DEATHS, PLAYER_KILL_COUNT
* - Bungee ServerContainer ServerKeys.TPS only contains playersOnline values
* - NetworkKeys.PLAYERS PlayerContainers:
* - do not support: PlayerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_KILL_COUNT
* - PlayerKeys.PER_SERVER does not support: PerServerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_KILL_COUNT
* - do not support: PlayerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_DEATHS, PLAYER_KILL_COUNT
* - PlayerKeys.PER_SERVER does not support: PerServerKeys WORLD_TIMES, PLAYER_KILLS, PLAYER_DEATHS, PLAYER_KILL_COUNT
* <p>
* Blocking methods are not called until DataContainer getter methods are called.
*

View File

@ -10,7 +10,7 @@ import com.djrapitops.plan.data.store.keys.PerServerKeys;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.keys.ServerKeys;
import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.data.store.mutators.PerServerDataMutator;
import com.djrapitops.plan.data.store.mutators.PerServerMutator;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.objects.DateObj;
@ -137,7 +137,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations {
// Calculating getters
container.putSupplier(PlayerKeys.WORLD_TIMES, () -> {
WorldTimes worldTimes = new PerServerDataMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes();
WorldTimes worldTimes = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes();
container.getValue(PlayerKeys.ACTIVE_SESSION)
.ifPresent(session -> worldTimes.add(
session.getValue(SessionKeys.WORLD_TIMES).orElse(new WorldTimes(new HashMap<>())))
@ -182,7 +182,7 @@ public class SQLFetchOps extends SQLOps implements FetchOperations {
container.putSupplier(PlayerKeys.PER_SERVER, () -> perServerInfo.get(uuid));
container.putSupplier(PlayerKeys.SESSIONS, () -> {
List<Session> playerSessions = PerServerDataMutator.forContainer(container).flatMapSessions();
List<Session> playerSessions = PerServerMutator.forContainer(container).flatMapSessions();
container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(playerSessions::add);
return playerSessions;
}
@ -259,18 +259,18 @@ public class SQLFetchOps extends SQLOps implements FetchOperations {
container.putSupplier(PlayerKeys.NICKNAMES, () -> nicknamesTable.getNicknameInformation(uuid));
container.putSupplier(PlayerKeys.PER_SERVER, () -> getPerServerData(uuid));
container.putSupplier(PlayerKeys.BANNED, () -> new PerServerDataMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isBanned());
container.putSupplier(PlayerKeys.OPERATOR, () -> new PerServerDataMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isOperator());
container.putSupplier(PlayerKeys.BANNED, () -> new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isBanned());
container.putSupplier(PlayerKeys.OPERATOR, () -> new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).isOperator());
container.putSupplier(PlayerKeys.SESSIONS, () -> {
List<Session> sessions = new PerServerDataMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapSessions();
List<Session> sessions = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapSessions();
container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(sessions::add);
return sessions;
}
);
container.putSupplier(PlayerKeys.WORLD_TIMES, () ->
{
WorldTimes worldTimes = new PerServerDataMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes();
WorldTimes worldTimes = new PerServerMutator(container.getUnsafe(PlayerKeys.PER_SERVER)).flatMapWorldTimes();
container.getValue(PlayerKeys.ACTIVE_SESSION).ifPresent(session -> worldTimes.add(
session.getValue(SessionKeys.WORLD_TIMES).orElse(new WorldTimes(new HashMap<>())))
);

View File

@ -10,7 +10,7 @@ import com.djrapitops.plan.data.store.containers.PerServerContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.PerServerDataMutator;
import com.djrapitops.plan.data.store.mutators.PerServerMutator;
import com.djrapitops.plan.data.store.mutators.PvpInfoMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
@ -107,15 +107,15 @@ public class InspectPage implements Page {
replacer.put("kickCount", timesKicked);
PerServerContainer perServerContainer = container.getValue(PlayerKeys.PER_SERVER).orElse(new PerServerContainer());
PerServerDataMutator perServerDataMutator = new PerServerDataMutator(perServerContainer);
PerServerMutator perServerMutator = new PerServerMutator(perServerContainer);
Map<UUID, WorldTimes> worldTimesPerServer = perServerDataMutator.worldTimesPerServer();
Map<UUID, WorldTimes> worldTimesPerServer = perServerMutator.worldTimesPerServer();
replacer.put("serverPieSeries", new ServerPreferencePie(serverNames, worldTimesPerServer).toHighChartsSeries());
replacer.put("worldPieColors", Theme.getValue(ThemeVal.GRAPH_WORLD_PIE));
replacer.put("gmPieColors", Theme.getValue(ThemeVal.GRAPH_GM_PIE));
replacer.put("serverPieColors", Theme.getValue(ThemeVal.GRAPH_SERVER_PREF_PIE));
String favoriteServer = serverNames.getOrDefault(perServerDataMutator.favoriteServer(), "Unknown");
String favoriteServer = serverNames.getOrDefault(perServerMutator.favoriteServer(), "Unknown");
replacer.put("favoriteServer", favoriteServer);
replacer.put("tableBodyNicknames", new NicknameTable(

View File

@ -36,7 +36,9 @@ public class NetworkPage implements Page {
PLAYERS_ALL_TIME_PEAK, PLAYERS_RECENT_PEAK,
PLAYERS_DAY, PLAYERS_WEEK, PLAYERS_MONTH,
PLAYERS_NEW_DAY, PLAYERS_NEW_WEEK, PLAYERS_NEW_MONTH,
WORLD_MAP_SERIES, WORLD_MAP_HIGH_COLOR, WORLD_MAP_LOW_COLOR
WORLD_MAP_SERIES, WORLD_MAP_HIGH_COLOR, WORLD_MAP_LOW_COLOR,
HEALTH_INDEX, HEALTH_NOTES,
ACTIVITY_PIE_SERIES, ACTIVITY_STACK_SERIES, ACTIVITY_STACK_CATEGORIES
);
NetworkPageContent networkPageContent = (NetworkPageContent)
ResponseCache.loadResponse(PageId.NETWORK_CONTENT.id(), NetworkPageContent::new);

View File

@ -113,6 +113,12 @@
<span>Servers</span>
</a>
</li>
<li>
<a class="nav-button" href="javascript:void(0)">
<i class="material-icons">local_hospital</i>
<span>Network Health</span>
</a>
</li>
<li>
<a class="nav-button" href="javascript:void(0)">
<i class="material-icons">language</i>
@ -333,6 +339,117 @@
${tabContentServers}
</div>
<!-- #END# Tab Servers -->
<div id="tab-health" class="tab">
<div class="row clearfix">
<!-- Health Gauge -->
<div class="col-xs-12 col-sm-12 col-md-4 col-lg-4">
<div class="card">
<div class="header">
<div class="row clearfix">
<div class="col-xs-6 col-sm-6 col-lg-6">
<h2><i class="col-red fa fa-heartbeat"></i> Health Estimate</h2>
</div>
<div class="col-xs-6 col-sm-6 col-lg-6">
<a href="javascript:void(0)" class="help material-icons pull-right"
tabindex="0" data-trigger="focus" data-toggle="popover" data-placement="left"
data-container="body" data-html="true"
data-original-title="Server Health Estimate"
data-content="Quick Measure of the server health.
<br><br>The health is calculated using different measures, all of which can be seen in the notes section."
>help_outline</a>
</div>
</div>
</div>
<div class="body">
<div id="healthGauge" style="height: 200px; width: 100%;"></div>
</div>
</div>
</div>
<!-- #END# Health Gauge -->
<!-- Notes -->
<div class="col-xs-12 col-sm-12 col-md-8 col-lg-8">
<div class="card">
<div class="header">
<div class="row clearfix">
<div class="col-xs-6 col-sm-6 col-lg-6">
<h2><i class="col-red far fa-life-ring"></i> Last 30 Days</h2>
</div>
<div class="col-xs-6 col-sm-6 col-lg-6">
<a href="javascript:void(0)" class="help material-icons pull-right"
tabindex="0" data-trigger="focus" data-toggle="popover" data-placement="left"
data-container="body" data-html="true"
data-original-title="Notes (30 Days)"
data-content="Measures the server health is based on
<br><br>Each measure has 3 possible outcomes:
<br>Thumbs up: All good
<br>Flag: Something might require action
<br>Warning: Measure is reducing the server health"
>help_outline</a>
</div>
</div>
</div>
<div class="body bg-white">
${healthNotes}
</div>
</div>
</div>
<!-- #END# Notes -->
</div>
<div class="row clearfix">
<div class="col-xs-12 col-sm-12 col-md-8 col-lg-8">
<div class="card">
<div class="header">
<div class="row clearfix">
<div class="col-xs-6 col-sm-6 col-lg-6">
<h2><i class="col-amber fa fa-line-chart"></i> Playerbase Development</h2>
</div>
<div class="col-xs-6 col-sm-6 col-lg-6">
<a href="javascript:void(0)" class="help material-icons pull-right"
tabindex="0" data-trigger="focus" data-toggle="popover" data-placement="left"
data-container="body" data-html="true"
data-original-title="Playerbase Development"
data-content="Chart that displays development of the playerbase
<br><br>Groups are based on Activity Index. <br><br><b>Activity Index:</b> Calculated using the sessions in the last 3 weeks (At that point in time). From 0 to 5.
<br><br>Points are calculated every 7 days for last 9 weeks, using the activity index at that point for each player.
<br><br><b>Groups:</b> Very Active(>&nbsp;3,5) Active(>&nbsp;1.75) Regular(>&nbsp;1.0) Irregular(>&nbsp;0.5) Inactive(<&nbsp;0.5)
<br><br>Groups can be hidden by clicking the group name in the legend."
>help_outline</a>
</div>
</div>
</div>
<div class="body">
<div id="activityStackGraph" class="dashboard-flot-chart"></div>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-4 col-lg-4">
<div class="card">
<div class="header">
<div class="row clearfix">
<div class="col-xs-6 col-sm-6 col-lg-6">
<h2><i class="col-amber fa fa-users"></i> Current Playerbase</h2>
</div>
<div class="col-xs-6 col-sm-6 col-lg-6">
<a href="javascript:void(0)" class="help material-icons pull-right"
tabindex="0" data-trigger="focus" data-toggle="popover" data-placement="left"
data-container="body" data-html="true"
data-original-title="Current Playerbase Distribution"
data-content="Pie of the Activity Index distribution at the time of Analysis, last point in the Playerbase Development graph.
<br><br><b>Activity Index:</b> Calculated using the sessions in the last 3 weeks. From 0 to 5.
<br><br><b>Groups:</b> Very Active(>&nbsp;3,5) Active(>&nbsp;1.75) Regular(>&nbsp;1.0) Irregular(>&nbsp;0.5) Inactive(<&nbsp;0.5)
<br><br>Groups can be hidden by clicking the group name in the legend."
>help_outline</a>
</div>
</div>
</div>
<div class="body">
<div id="activityPie" class="dashboard-donut-chart"></div>
</div>
</div>
</div>
</div>
</div>
<!-- #END# Tab Health -->
<div id="tab-geolocations" class="tab">
<!-- Geolocations -->
<div class="row clearfix">
@ -381,6 +498,8 @@
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/maps/modules/map.js"></script>
<script src="https://code.highcharts.com/mapdata/custom/world.js"></script>
<script src="https://code.highcharts.com/highcharts-more.js"></script>
<script src="https://code.highcharts.com/modules/solid-gauge.js"></script>
<script src="https://code.highcharts.com/modules/no-data-to-display.js"></script>
<!-- Header, Sidenav & Skin changer -->
@ -388,7 +507,10 @@
<!-- Plan Charts -->
<script src="../js/charts/playerGraph.js"></script>
<script src="js/charts/worldMap.js"></script>
<script src="../js/charts/healthGauge.js"></script>
<script src="../js/charts/activityPie.js"></script>
<script src="../js/charts/stackGraph.js"></script>
<script src="../js/charts/worldMap.js"></script>
<!-- Chart Data -->
<script>
@ -398,24 +520,47 @@
timezoneOffset: ${timeZone} * 60
}
})
var geolocationsLow = '${worldMapColLow}';
var geolocationsHigh = '${worldMapColHigh}';
// Data Variables
var playersOnlineSeries = {
name: 'Players Online',
data: ${playersOnlineSeries},
type: 'areaspline',
color: '${playersGraphColor}',
tooltip: {
valueDecimals: 0
// Placeholder values
var v = {
colors: {
playersOnline: '${playersGraphColor}',
geolocationsLow: '${worldMapColLow}',
geolocationsHigh: '${worldMapColHigh}'
},
data: {
playersOnline: ${playersOnlineSeries},
activityPie: ${activityPieSeries},
geolocations: ${geoMapSeries},
activityStack: ${activityStackSeries},
activityStackCategories: ${activityStackCategories},
healthIndex: ${healthIndex}
}
};
var geolocationsSeries = {
name: 'Players',
type: 'map',
mapData: Highcharts.maps['custom/world'],
data: ${geoMapSeries},
joinBy: ['iso-a3', 'code']
// HighCharts Series
var series = {
playersOnline: {
name: 'Players Online',
data: ${playersOnlineSeries},
type: 'areaspline',
color: '${playersGraphColor}',
tooltip: {
valueDecimals: 0
}
},
geolocations: {
name: 'Players',
type: 'map',
mapData: Highcharts.maps['custom/world'],
data: ${geoMapSeries},
joinBy: ['iso-a3', 'code']
},
activityPie: {
name: 'Players',
colorByPoint: true,
data: v.data.activityPie
},
activityStack: v.data.activityStack,
activityStackCategories: v.data.activityStackCategories
};
</script>
@ -440,8 +585,11 @@
openFunc(slideIndex)();
// Chart draw scripts
playersChart('playerChartDay', playersOnlineSeries, 2);
worldMap('worldMap', geolocationsLow, geolocationsHigh, geolocationsSeries);
playersChart('playerChartDay', series.playersOnline, 2);
activityPie('activityPie', series.activityPie);
stackChart('activityStackGraph', series.activityStackCategories, series.activityStack, 'Players');
healthGauge('healthGauge', [v.data.healthIndex]);
worldMap('worldMap', v.colors.geolocationsLow, v.colors.geolocationsHigh, series.geolocations);
function openFunc(i) {
return function () {