mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-07 17:28:03 +08:00
Started work on network health tab #601
This commit is contained in:
parent
46fa30d224
commit
b6fc279649
@ -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;
|
||||
|
@ -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")
|
||||
);
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
|
||||
}
|
@ -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");
|
||||
|
@ -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 */
|
||||
}
|
||||
|
@ -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 = " ";
|
||||
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 = " ";
|
||||
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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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() {
|
@ -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);
|
||||
|
@ -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 = " ";
|
||||
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();
|
||||
}
|
||||
}
|
@ -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 = " ";
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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<>())))
|
||||
);
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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(> 3,5) Active(> 1.75) Regular(> 1.0) Irregular(> 0.5) Inactive(< 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(> 3,5) Active(> 1.75) Regular(> 1.0) Irregular(> 0.5) Inactive(< 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 () {
|
||||
|
Loading…
Reference in New Issue
Block a user