mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-15 05:41:51 +08:00
Health Information
This commit is contained in:
parent
566f838a3a
commit
201cb98055
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* @author Rsl1122
|
* @author Rsl1122
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class HealthNotes {
|
public class HealthNotes {
|
||||||
|
|
||||||
private final List<String> notes;
|
private final List<String> notes;
|
||||||
|
@ -59,6 +59,7 @@ public class AnalysisContainer extends DataContainer {
|
|||||||
addGraphSuppliers();
|
addGraphSuppliers();
|
||||||
addTPSAverageSuppliers();
|
addTPSAverageSuppliers();
|
||||||
addCommandSuppliers();
|
addCommandSuppliers();
|
||||||
|
addServerHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addConstants() {
|
private void addConstants() {
|
||||||
@ -341,4 +342,11 @@ public class AnalysisContainer extends DataContainer {
|
|||||||
putSupplier(AnalysisKeys.COMMAND_COUNT_UNIQUE, () -> serverContainer.getValue(ServerKeys.COMMAND_USAGE).map(Map::size).orElse(0));
|
putSupplier(AnalysisKeys.COMMAND_COUNT_UNIQUE, () -> serverContainer.getValue(ServerKeys.COMMAND_USAGE).map(Map::size).orElse(0));
|
||||||
putSupplier(AnalysisKeys.COMMAND_COUNT, () -> CommandUseMutator.forContainer(serverContainer).commandUsageCount());
|
putSupplier(AnalysisKeys.COMMAND_COUNT, () -> CommandUseMutator.forContainer(serverContainer).commandUsageCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addServerHealth() {
|
||||||
|
Key<HealthInformation> healthInformation = new Key<>(HealthInformation.class, "HEALTH_INFORMATION");
|
||||||
|
putSupplier(healthInformation, () -> new HealthInformation(this));
|
||||||
|
putSupplier(AnalysisKeys.HEALTH_INDEX, () -> getUnsafe(healthInformation).getServerHealth());
|
||||||
|
putSupplier(AnalysisKeys.HEALTH_NOTES, () -> getUnsafe(healthInformation).toHtml());
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,255 @@
|
|||||||
|
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.analysis.MathUtils;
|
||||||
|
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<>();
|
||||||
|
calculate();
|
||||||
|
|
||||||
|
now = analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME);
|
||||||
|
fourWeeksAgo = analysisContainer.getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = " ";
|
||||||
|
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 stuckPerc = MathUtils.averageDouble(playersRetainedMonth, playersNewMonth) * 100;
|
||||||
|
if (stuckPerc >= 25) {
|
||||||
|
notes.add("<p>" + Html.GREEN_THUMB.parse() + " " + FormatUtils.cutDecimals(stuckPerc)
|
||||||
|
+ "% of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
|
||||||
|
} else {
|
||||||
|
notes.add("<p>" + Html.YELLOW_FLAG.parse() + " " + FormatUtils.cutDecimals(stuckPerc)
|
||||||
|
+ "% of new players have stuck around (" + playersRetainedMonth + "/" + playersNewMonth + ")</p>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activePlayerPlaytimeChange() {
|
||||||
|
PlayersMutator currentlyActive = PlayersMutator.copyOf(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 = MathUtils.averageLong(totalFourToTwoWeeks, activeCount);
|
||||||
|
long avgLastTwoWeeks = MathUtils.averageLong(totalLastTwoWeeks, 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 = " ";
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -67,6 +67,13 @@ public class PlayersMutator {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlayersMutator filterActive(long date, double limit) {
|
||||||
|
players = players.stream()
|
||||||
|
.filter(player -> new ActivityIndex(player, date).getValue() >= limit)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PlayerContainer> all() {
|
public List<PlayerContainer> all() {
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
@ -32,4 +32,9 @@ public class PlayersOnlineResolver {
|
|||||||
}
|
}
|
||||||
return Optional.of(entry.getValue());
|
return Optional.of(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isServerOnline(long date, long timeLimit) {
|
||||||
|
Long lastEntry = onlineNumberMap.floorKey(date);
|
||||||
|
return date - lastEntry < timeLimit;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user