Players online & Day by Day graph to network page

This commit is contained in:
Rsl1122 2019-08-15 20:27:47 +03:00
parent 20e8a13ef9
commit 3d1e32fe88
8 changed files with 322 additions and 10 deletions

View File

@ -51,6 +51,11 @@ public class MutatorFunctions {
filled.put(point.getKey(), point.getValue());
}
long now = System.currentTimeMillis();
if (lastX != null && now - lastX > accuracy) {
addMissing(lastX, now, filled, accuracy, replacement);
}
return filled;
}
@ -62,6 +67,32 @@ public class MutatorFunctions {
}
}
public static List<Point> addMissing(List<Point> points, long accuracy, Integer replacement) {
if (Verify.isEmpty(points)) return points;
List<Point> filled = new ArrayList<>();
Long lastX = null;
for (Point point : points) {
long date = (long) point.getX();
if (lastX != null && date - lastX > accuracy) {
addMissing(lastX, date, filled, accuracy, replacement);
}
lastX = date;
filled.add(point);
}
return filled;
}
private static void addMissing(long from, long to, List<Point> points, long accuracy, Integer replacement) {
long iterate = from;
while (iterate < to) {
points.add(new Point(iterate, replacement));
iterate += accuracy;
}
}
public static int average(Map<Long, Integer> map) {
return (int) map.values().stream()
.mapToInt(i -> i)

View File

@ -143,6 +143,46 @@ public class PlayerCountQueries {
};
}
/**
* Fetch a EpochMs - Count map of unique players on ALL servers.
*
* @param after After epoch ms
* @param before Before epoch ms
* @param timeZoneOffset Offset from {@link java.util.TimeZone#getOffset(long)}, applied to the dates before grouping.
* @return Map: Epoch ms (Accuracy of a day) - How many unique players played that day
*/
public static Query<NavigableMap<Long, Integer>> uniquePlayerCounts(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectUniquePlayersPerDay = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
"COUNT(DISTINCT " + SessionsTable.USER_UUID + ") as player_count" +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + "<=?" +
AND + SessionsTable.SESSION_START + ">=?" +
GROUP_BY + "date";
return database.query(new QueryStatement<NavigableMap<Long, Integer>>(selectUniquePlayersPerDay, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, timeZoneOffset);
statement.setLong(2, before);
statement.setLong(3, after);
}
@Override
public NavigableMap<Long, Integer> processResults(ResultSet set) throws SQLException {
NavigableMap<Long, Integer> uniquePerDay = new TreeMap<>();
while (set.next()) {
uniquePerDay.put(set.getLong("date"), set.getInt("player_count"));
}
return uniquePerDay;
}
});
};
}
public static Query<Integer> averageUniquePlayerCount(long after, long before, long timeZoneOffset, UUID serverUUID) {
return database -> {
Sql sql = database.getSql();
@ -236,6 +276,38 @@ public class PlayerCountQueries {
};
}
public static Query<NavigableMap<Long, Integer>> newPlayerCounts(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectNewPlayersQuery = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) +
"*1000 as date," +
"COUNT(1) as player_count" +
FROM + UsersTable.TABLE_NAME +
WHERE + UsersTable.REGISTERED + "<=?" +
AND + UsersTable.REGISTERED + ">=?" +
GROUP_BY + "date";
return database.query(new QueryStatement<NavigableMap<Long, Integer>>(selectNewPlayersQuery, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, timeZoneOffset);
statement.setLong(2, before);
statement.setLong(3, after);
}
@Override
public NavigableMap<Long, Integer> processResults(ResultSet set) throws SQLException {
NavigableMap<Long, Integer> newPerDay = new TreeMap<>();
while (set.next()) {
newPerDay.put(set.getLong("date"), set.getInt("player_count"));
}
return newPerDay;
}
});
};
}
public static Query<Integer> averageNewPlayerCount(long after, long before, long timeZoneOffset, UUID serverUUID) {
return database -> {
Sql sql = database.getSql();

View File

@ -118,6 +118,67 @@ public class TPSQueries {
};
}
public static Query<List<DateObj<Integer>>> fetchPlayersOnlineOfServer(long after, long before, UUID serverUUID) {
String sql = SELECT + ServerTable.SERVER_UUID + ',' + DATE + ',' + PLAYERS_ONLINE +
FROM + TABLE_NAME +
INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_ID + '=' + SERVER_ID +
WHERE + ServerTable.SERVER_UUID + "=?" +
AND + DATE + "<?" +
AND + DATE + ">?";
return new QueryStatement<List<DateObj<Integer>>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
statement.setLong(2, before);
statement.setLong(3, after);
}
@Override
public List<DateObj<Integer>> processResults(ResultSet set) throws SQLException {
List<DateObj<Integer>> ofServer = new ArrayList<>();
while (set.next()) {
ofServer.add(new DateObj<>(set.getLong(DATE), set.getInt(PLAYERS_ONLINE)));
}
return ofServer;
}
};
}
public static Query<Map<UUID, List<DateObj<Integer>>>> fetchPlayersOnlineOfAllServersBut(long after, long before, UUID leaveOut) {
String sql = SELECT + ServerTable.SERVER_UUID + ',' + DATE + ',' + PLAYERS_ONLINE +
FROM + TABLE_NAME +
INNER_JOIN + ServerTable.TABLE_NAME + " on " + ServerTable.TABLE_NAME + '.' + ServerTable.SERVER_ID + '=' + SERVER_ID +
WHERE + ServerTable.SERVER_UUID + "!=?" +
AND + ServerTable.INSTALLED + "=?" +
AND + DATE + "<?" +
AND + DATE + ">?";
return new QueryStatement<Map<UUID, List<DateObj<Integer>>>>(sql, 1000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, leaveOut.toString());
statement.setBoolean(2, true);
statement.setLong(3, before);
statement.setLong(4, after);
}
@Override
public Map<UUID, List<DateObj<Integer>>> processResults(ResultSet set) throws SQLException {
Map<UUID, List<DateObj<Integer>>> byServer = new HashMap<>();
while (set.next()) {
UUID serverUUID = UUID.fromString(set.getString(ServerTable.SERVER_UUID));
List<DateObj<Integer>> ofServer = byServer.getOrDefault(serverUUID, new ArrayList<>());
ofServer.add(new DateObj<>(set.getLong(DATE), set.getInt(PLAYERS_ONLINE)));
byServer.put(serverUUID, ofServer);
}
return byServer;
}
};
}
public static Query<Map<Integer, List<TPS>>> fetchPlayerOnlineDataOfServers(Collection<Server> servers) {
if (servers.isEmpty()) {
return db -> new HashMap<>();

View File

@ -30,11 +30,14 @@ import com.djrapitops.plan.db.access.queries.analysis.PlayerCountQueries;
import com.djrapitops.plan.db.access.queries.objects.*;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DisplaySettings;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.utilities.html.graphs.Graphs;
import com.djrapitops.plan.utilities.html.graphs.bar.BarGraph;
import com.djrapitops.plan.utilities.html.graphs.line.LineGraph;
import com.djrapitops.plan.utilities.html.graphs.line.LineGraphFactory;
import com.djrapitops.plan.utilities.html.graphs.line.PingGraph;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import com.djrapitops.plan.utilities.html.graphs.pie.Pie;
import com.djrapitops.plan.utilities.html.graphs.pie.WorldPie;
import com.djrapitops.plan.utilities.html.graphs.special.WorldMap;
@ -45,6 +48,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Perses Graph related Data JSON.
@ -74,8 +78,10 @@ public class GraphJSONParser {
public String performanceGraphJSON(UUID serverUUID) {
Database db = dbSystem.getDatabase();
LineGraphFactory lineGraphs = graphs.line();
long now = System.currentTimeMillis();
long halfYearAgo = now - TimeUnit.DAYS.toMillis(180L);
TPSMutator tpsMutator = new TPSMutator(db.query(TPSQueries.fetchTPSDataOfServer(serverUUID)))
.filterDataBetween(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(180L), System.currentTimeMillis());
.filterDataBetween(halfYearAgo, now);
return '{' +
"\"playersOnline\":" + lineGraphs.playersOnlineGraph(tpsMutator).toHighChartsSeries() +
",\"tps\":" + lineGraphs.tpsGraph(tpsMutator).toHighChartsSeries() +
@ -87,6 +93,18 @@ public class GraphJSONParser {
'}';
}
public String playersOnlineGraph(UUID serverUUID) {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();
long halfYearAgo = now - TimeUnit.DAYS.toMillis(180L);
Boolean displayGaps = config.get(DisplaySettings.GAPS_IN_GRAPH_DATA);
List<Point> points = db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, serverUUID)).stream()
.map(point -> new Point(point.getDate(), point.getValue()))
.collect(Collectors.toList());
return "{\"playersOnline\":" + new LineGraph(points, displayGaps).toHighChartsSeries() + '}';
}
public String uniqueAndNewGraphJSON(UUID serverUUID) {
Database db = dbSystem.getDatabase();
LineGraphFactory lineGraphs = graphs.line();
@ -112,6 +130,31 @@ public class GraphJSONParser {
'}';
}
public String uniqueAndNewGraphJSON() {
Database db = dbSystem.getDatabase();
LineGraphFactory lineGraphs = graphs.line();
long now = System.currentTimeMillis();
long halfYearAgo = now - TimeUnit.DAYS.toMillis(180L);
NavigableMap<Long, Integer> uniquePerDay = db.query(
PlayerCountQueries.uniquePlayerCounts(halfYearAgo, now, timeZone.getOffset(now))
);
NavigableMap<Long, Integer> newPerDay = db.query(
PlayerCountQueries.newPlayerCounts(halfYearAgo, now, timeZone.getOffset(now))
);
return "{\"uniquePlayers\":" +
lineGraphs.lineGraph(MutatorFunctions.toPointsWithRemovedOffset(
MutatorFunctions.addMissing(uniquePerDay, TimeUnit.DAYS.toMillis(1L), 0),
timeZone
)).toHighChartsSeries() +
",\"newPlayers\":" +
lineGraphs.lineGraph(MutatorFunctions.toPointsWithRemovedOffset(
MutatorFunctions.addMissing(newPerDay, TimeUnit.DAYS.toMillis(1L), 0),
timeZone
)).toHighChartsSeries() +
'}';
}
public String serverCalendarJSON(UUID serverUUID) {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();

View File

@ -69,6 +69,8 @@ public class GraphsJSONHandler implements PageHandler {
switch (type) {
case "performance":
return new JSONResponse(graphJSON.performanceGraphJSON(serverUUID));
case "playersOnline":
return new JSONResponse(graphJSON.playersOnlineGraph(serverUUID));
case "uniqueAndNew":
return new JSONResponse(graphJSON.uniqueAndNewGraphJSON(serverUUID));
case "serverCalendar":
@ -92,6 +94,8 @@ public class GraphsJSONHandler implements PageHandler {
switch (type) {
case "activity":
return new JSONResponse(graphJSON.activityGraphsJSONAsMap());
case "uniqueAndNew":
return new JSONResponse(graphJSON.uniqueAndNewGraphJSON());
default:
throw new BadRequestException("unknown 'type' parameter: " + type);
}

View File

@ -16,6 +16,7 @@
*/
package com.djrapitops.plan.utilities.html.graphs.line;
import com.djrapitops.plan.data.store.mutators.MutatorFunctions;
import com.djrapitops.plan.utilities.html.graphs.HighChart;
import java.util.List;
@ -62,6 +63,13 @@ public class LineGraph implements HighChart {
return arrayBuilder.toString();
}
public List<Point> getPoints() {
if (displayGaps) {
return MutatorFunctions.addMissing(points, TimeUnit.MINUTES.toMillis(1L), null);
}
return points;
}
private void addMissingPoints(StringBuilder arrayBuilder, Long lastX, long date) {
long iterate = lastX + TimeUnit.MINUTES.toMillis(1L);
while (iterate < date) {

View File

@ -31,6 +31,7 @@ import com.djrapitops.plan.utilities.formatting.Formatters;
import com.djrapitops.plan.utilities.formatting.PlaceholderReplacer;
import java.util.List;
import java.util.UUID;
/**
* Html String parser for /network page.
@ -71,16 +72,23 @@ public class NetworkPage implements Page {
try {
PlaceholderReplacer placeholders = new PlaceholderReplacer();
UUID serverUUID = serverInfo.getServerUUID();
placeholders.put("networkDisplayName", config.get(ProxySettings.NETWORK_NAME));
placeholders.put("serverUUID", serverUUID.toString());
placeholders.put("gmPieColors", theme.getValue(ThemeVal.GRAPH_GM_PIE));
placeholders.put("playersGraphColor", theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE));
placeholders.put("worldMapColLow", theme.getValue(ThemeVal.WORLD_MAP_LOW));
placeholders.put("worldMapColHigh", theme.getValue(ThemeVal.WORLD_MAP_HIGH));
placeholders.put("maxPingColor", theme.getValue(ThemeVal.GRAPH_MAX_PING));
placeholders.put("minPingColor", theme.getValue(ThemeVal.GRAPH_MIN_PING));
placeholders.put("avgPingColor", theme.getValue(ThemeVal.GRAPH_AVG_PING));
placeholders.put("timeZone", config.getTimeZoneOffsetHours());
placeholders.put("update", versionCheckSystem.getUpdateHtml().orElse(""));
List<ExtensionServerData> extensionData = dbSystem.getDatabase()
.query(new ExtensionServerDataQuery(serverInfo.getServerUUID()));
.query(new ExtensionServerDataQuery(serverUUID));
ServerPluginTabs pluginTabs = new ServerPluginTabs(extensionData, formatters);
String nav = pluginTabs.getNav();

View File

@ -154,18 +154,37 @@
<!-- Online Activity Chart -->
<div class="col-xl-9 col-lg-9 col-sm-12">
<div class="card shadow mb-4">
<div
class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold col-black"><i
class="fas fa-fw fa-chart-area col-blue"></i>
Network Online Activity</h6>
</div>
<div class="chart-area">
<canvas id="myAreaChart"></canvas>
<ul class="nav nav-tabs" id="onlineActivityTab" role="tablist">
<li class="nav-item">
<a aria-controls="profile" aria-selected="false"
class="nav-link active col-black"
data-toggle="tab"
href="#online-tab" id="online-online-tab" role="tab">
<i class="fas fa-fw fa-chart-area col-blue"></i> Network Online Activity</a>
</li>
<li class="nav-item">
<a aria-controls="home" aria-selected="true" class="nav-link col-black"
data-toggle="tab" href="#unique-tab" id="online-unique-tab" role="tab"><i
class="fa fa-fw fa-chart-area col-blue"></i> Day by Day</a>
</li>
</ul>
<div class="tab-content" id="onlineActivityTabContent">
<div aria-labelledby="online-online-tab" class="tab-pane fade show active"
id="online-tab"
role="tabpanel">
<div class="chart-area" id="playersOnlineChart"><span class="loader"></span>
</div>
</div>
<div aria-labelledby="online-unique-tab" class="tab-pane fade"
id="unique-tab"
role="tabpanel">
<div class="chart-area" id="uniqueChart"><span class="loader"></span></div>
</div>
</div>
</div>
</div>
<!-- Players -->
<div class="col-xl-3 col-lg-3 col-sm-12">
<div class="card shadow mb-4">
@ -1289,6 +1308,72 @@
jsonRequest("../v1/network/playerbaseOverview", loadPlayerbaseOverviewValues);
setLoadingText('Rendering graphs..');
var v = {
colors: {
playersOnline: '${playersGraphColor}',
newPlayers: '#8BC34A',
geolocationsLow: '${worldMapColLow}',
geolocationsHigh: '${worldMapColHigh}',
maxPing: '${maxPingColor}',
minPing: '${minPingColor}',
avgPing: '${avgPingColor}'
}
};
// HighCharts Series
var s = {
name: {
playersOnline: 'Players Online',
uniquePlayers: 'Unique Players',
newPlayers: 'New Players',
maxPing: 'Worst Ping',
minPing: 'Best Ping',
avgPing: 'Average Ping'
},
tooltip: {
twoDecimals: {
valueDecimals: 2
},
zeroDecimals: {
valueDecimals: 0
}
},
type: {
areaSpline: 'areaspline',
spline: 'spline'
}
};
jsonRequest("../v1/graph?type=playersOnline&server=${serverUUID}", function (data, error) {
if (data) {
var series = {
playersOnline: {
name: s.name.playersOnline, type: s.type.areaSpline, tooltip: s.tooltip.zeroDecimals,
data: data.playersOnline, color: v.colors.playersOnline, yAxis: 0
}
};
playersChart('playersOnlineChart', series.playersOnline, 2);
} else if (error) {
$('#playersOnlineChart').text("Failed to load graph data: " + error);
}
});
jsonRequest("../v1/graph?type=uniqueAndNew", function (data, error) {
if (data) {
var uniquePlayers = {
name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: data.uniquePlayers, color: v.colors.playersOnline
};
var newPlayers = {
name: s.name.newPlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: data.newPlayers, color: v.colors.newPlayers
};
lineChart('uniqueChart', [uniquePlayers, newPlayers]);
} else if (error) {
$('#uniqueChart').text("Failed to load graph data: " + error)
}
});
jsonRequest("../v1/graph?type=activity", function (json, error) {
if (json) {
activityPie('activityPie', {