Implemented a Hour by Hour graph

- Unique players, New players per hour

Affects issues:
- #1232
This commit is contained in:
Risto Lahtela 2020-05-15 21:48:35 +03:00
parent 93dc1bbb9e
commit fb2bf97cc6
7 changed files with 282 additions and 8 deletions

View File

@ -134,17 +134,33 @@ public class GraphJSONCreator {
PlayerCountQueries.newPlayerCounts(halfYearAgo, now, timeZoneOffset, serverUUID)
);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay, TimeUnit.DAYS.toMillis(1L));
}
public String createUniqueAndNewJSON(LineGraphFactory lineGraphs, NavigableMap<Long, Integer> uniquePerDay, NavigableMap<Long, Integer> newPerDay) {
public String hourlyUniqueAndNewGraphJSON(UUID serverUUID) {
Database db = dbSystem.getDatabase();
LineGraphFactory lineGraphs = graphs.line();
long now = System.currentTimeMillis();
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);
int timeZoneOffset = config.getTimeZone().getOffset(now);
NavigableMap<Long, Integer> uniquePerDay = db.query(
PlayerCountQueries.hourlyUniquePlayerCounts(weekAgo, now, timeZoneOffset, serverUUID)
);
NavigableMap<Long, Integer> newPerDay = db.query(
PlayerCountQueries.newPlayerCounts(weekAgo, now, timeZoneOffset, serverUUID)
);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay, TimeUnit.HOURS.toMillis(1L));
}
public String createUniqueAndNewJSON(LineGraphFactory lineGraphs, NavigableMap<Long, Integer> uniquePerDay, NavigableMap<Long, Integer> newPerDay, long gapFillPeriod) {
return "{\"uniquePlayers\":" +
lineGraphs.lineGraph(MutatorFunctions.toPoints(
MutatorFunctions.addMissing(uniquePerDay, TimeUnit.DAYS.toMillis(1L), 0)
MutatorFunctions.addMissing(uniquePerDay, gapFillPeriod, 0)
)).toHighChartsSeries() +
",\"newPlayers\":" +
lineGraphs.lineGraph(MutatorFunctions.toPoints(
MutatorFunctions.addMissing(newPerDay, TimeUnit.DAYS.toMillis(1L), 0)
MutatorFunctions.addMissing(newPerDay, gapFillPeriod, 0)
)).toHighChartsSeries() +
",\"colors\":{" +
"\"playersOnline\":\"" + theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE) + "\"," +
@ -165,7 +181,23 @@ public class GraphJSONCreator {
PlayerCountQueries.newPlayerCounts(halfYearAgo, now, timeZoneOffset)
);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay, TimeUnit.DAYS.toMillis(1L));
}
public String hourlyUniqueAndNewGraphJSON() {
Database db = dbSystem.getDatabase();
LineGraphFactory lineGraphs = graphs.line();
long now = System.currentTimeMillis();
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);
int timeZoneOffset = config.getTimeZone().getOffset(now);
NavigableMap<Long, Integer> uniquePerDay = db.query(
PlayerCountQueries.hourlyUniquePlayerCounts(weekAgo, now, timeZoneOffset)
);
NavigableMap<Long, Integer> newPerDay = db.query(
PlayerCountQueries.hourlyNewPlayerCounts(weekAgo, now, timeZoneOffset)
);
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay, TimeUnit.HOURS.toMillis(1L));
}
public String serverCalendarJSON(UUID serverUUID) {

View File

@ -32,6 +32,7 @@ public enum DataID {
GRAPH_PERFORMANCE,
GRAPH_ONLINE,
GRAPH_UNIQUE_NEW,
GRAPH_HOURLY_UNIQUE_NEW,
GRAPH_CALENDAR,
GRAPH_WORLD_PIE,
GRAPH_WORLD_MAP,

View File

@ -92,6 +92,8 @@ public class GraphsJSONResolver implements Resolver {
return DataID.GRAPH_ONLINE;
case "uniqueAndNew":
return DataID.GRAPH_UNIQUE_NEW;
case "hourlyUniqueAndNew":
return DataID.GRAPH_HOURLY_UNIQUE_NEW;
case "serverCalendar":
return DataID.GRAPH_CALENDAR;
case "worldPie":
@ -119,6 +121,8 @@ public class GraphsJSONResolver implements Resolver {
return graphJSON.playersOnlineGraph(serverUUID);
case GRAPH_UNIQUE_NEW:
return graphJSON.uniqueAndNewGraphJSON(serverUUID);
case GRAPH_HOURLY_UNIQUE_NEW:
return graphJSON.hourlyUniqueAndNewGraphJSON(serverUUID);
case GRAPH_CALENDAR:
return graphJSON.serverCalendarJSON(serverUUID);
case GRAPH_WORLD_PIE:
@ -142,6 +146,8 @@ public class GraphsJSONResolver implements Resolver {
return graphJSON.activityGraphsJSONAsMap();
case GRAPH_UNIQUE_NEW:
return graphJSON.uniqueAndNewGraphJSON();
case GRAPH_HOURLY_UNIQUE_NEW:
return graphJSON.hourlyUniqueAndNewGraphJSON();
case GRAPH_SERVER_PIE:
return graphJSON.serverPreferencePieJSONAsMap();
case GRAPH_WORLD_MAP:

View File

@ -166,6 +166,49 @@ public class PlayerCountQueries {
};
}
/**
* Fetch a EpochMs - Count map of unique players on a server.
*
* @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.
* @param serverUUID UUID of the Plan server
* @return Map: Epoch ms (Start of day at 0 AM, no offset) - How many unique players played that day
*/
public static Query<NavigableMap<Long, Integer>> hourlyUniquePlayerCounts(long after, long before, long timeZoneOffset, UUID serverUUID) {
return database -> {
Sql sql = database.getSql();
String selectUniquePlayersPerDay = SELECT +
sql.dateToEpochSecond(sql.dateToHourStamp(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 + ">=?" +
AND + SessionsTable.SERVER_UUID + "=?" +
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);
statement.setString(4, serverUUID.toString());
}
@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;
}
});
};
}
/**
* Fetch a EpochMs - Count map of unique players on ALL servers.
*
@ -206,6 +249,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 (Start of day at 0 AM, no offset) - How many unique players played that day
*/
public static Query<NavigableMap<Long, Integer>> hourlyUniquePlayerCounts(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectUniquePlayersPerDay = SELECT +
sql.dateToEpochSecond(sql.dateToHourStamp(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();
@ -324,6 +407,49 @@ public class PlayerCountQueries {
};
}
/**
* Fetch a EpochMs - Count map of new players on a server.
*
* @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.
* @param serverUUID UUID of the Plan server
* @return Map: Epoch ms (Start of day at 0 AM, no offset) - How many new players joined that day
*/
public static Query<NavigableMap<Long, Integer>> hourlyNewPlayerCounts(long after, long before, long timeZoneOffset, UUID serverUUID) {
return database -> {
Sql sql = database.getSql();
String selectNewPlayersQuery = SELECT +
sql.dateToEpochSecond(sql.dateToHourStamp(sql.epochSecondToDate('(' + UserInfoTable.REGISTERED + "+?)/1000"))) +
"*1000 as date," +
"COUNT(1) as player_count" +
FROM + UserInfoTable.TABLE_NAME +
WHERE + UserInfoTable.REGISTERED + "<=?" +
AND + UserInfoTable.REGISTERED + ">=?" +
AND + UserInfoTable.SERVER_UUID + "=?" +
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);
statement.setString(4, serverUUID.toString());
}
@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;
}
});
};
}
/**
* Fetch a EpochMs - Count map of new players on a server.
*
@ -364,6 +490,46 @@ public class PlayerCountQueries {
};
}
/**
* Fetch a EpochMs - Count map of new players on a server.
*
* @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 (Start of day at 0 AM, no offset) - How many new players joined that day
*/
public static Query<NavigableMap<Long, Integer>> hourlyNewPlayerCounts(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectNewPlayersQuery = SELECT +
sql.dateToEpochSecond(sql.dateToHourStamp(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

@ -79,6 +79,8 @@ public abstract class Sql {
public abstract String dateToDayStamp(String sql);
public abstract String dateToHourStamp(String sql);
public abstract String dateToDayOfWeek(String sql);
public abstract String dateToHour(String sql);
@ -101,6 +103,11 @@ public abstract class Sql {
return "DATE(" + sql + ')';
}
@Override
public String dateToHourStamp(String sql) {
return "DATE_FORMAT(" + sql + ",'%Y-%m-%d %H:00:00')";
}
@Override
public String dateToDayOfWeek(String sql) {
return "DAYOFWEEK(" + sql + ')';
@ -130,6 +137,11 @@ public abstract class Sql {
return "DAY_OF_WEEK(" + sql + ')';
}
@Override
public String dateToHourStamp(String sql) {
return "DATE_FORMAT(" + sql + ",'yyyy-MM-dd HH:00:00')";
}
@Override
public String dateToHour(String sql) {
return "HOUR(" + sql + ')';
@ -154,6 +166,11 @@ public abstract class Sql {
return "strftime('%Y-%m-%d'," + sql + ')';
}
@Override
public String dateToHourStamp(String sql) {
return "strftime('%Y-%m-%d %H:00:00'," + sql + ')';
}
@Override
public String dateToDayOfWeek(String sql) {
return "strftime('%w'," + sql + ")+1";

View File

@ -162,6 +162,12 @@
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>
<li class="nav-item">
<a aria-controls="home" aria-selected="true" class="nav-link col-black"
data-toggle="tab" href="#hourly-unique-tab" id="online-hourly-unique-tab"
role="tab"><i
class="fa fa-fw fa-chart-area col-blue"></i> Hour by Hour</a>
</li>
</ul>
<div class="tab-content" id="onlineActivityTabContent">
<div aria-labelledby="online-online-tab" class="tab-pane show active"
@ -175,6 +181,11 @@
role="tabpanel">
<div class="chart-area" id="uniqueChart"><span class="loader"></span></div>
</div>
<div aria-labelledby="online-unique-tab" class="tab-pane"
id="hourly-unique-tab" role="tabpanel">
<div class="chart-area" id="hourlyUniqueChart"><span class="loader"></span>
</div>
</div>
</div>
</div>
</div>
@ -887,6 +898,22 @@
}
});
jsonRequest("./v1/graph?type=hourlyUniqueAndNew", function (json, error) {
if (json) {
var uniquePlayers = {
name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.uniquePlayers, color: v.colors.playersOnline
};
var newPlayers = {
name: s.name.newPlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.newPlayers, color: v.colors.newPlayers
};
dayByDay('hourlyUniqueChart', [uniquePlayers, newPlayers]);
} else if (error) {
$('#uniqueChart').text("Failed to load graph data: " + error)
}
});
jsonRequest("./v1/graph?type=serverPie", function (json, error) {
if (json) {
serverPieSeries = {

View File

@ -333,6 +333,12 @@
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>
<li class="nav-item">
<a aria-controls="home" aria-selected="true" class="nav-link col-black"
data-toggle="tab" href="#hourly-unique-tab" id="online-hourly-unique-tab"
role="tab"><i
class="fa fa-fw fa-chart-area col-blue"></i> Hour by Hour</a>
</li>
<li class="nav-item">
<a aria-controls="profile" aria-selected="false" class="nav-link col-black"
data-toggle="tab"
@ -348,10 +354,14 @@
</ul>
<div class="tab-content" id="onlineActivityTabContent">
<div aria-labelledby="online-unique-tab" class="tab-pane show active"
id="unique-tab"
role="tabpanel">
id="unique-tab" role="tabpanel">
<div class="chart-area" id="uniqueChart"><span class="loader"></span></div>
</div>
<div aria-labelledby="online-unique-tab" class="tab-pane"
id="hourly-unique-tab" role="tabpanel">
<div class="chart-area" id="hourlyUniqueChart"><span class="loader"></span>
</div>
</div>
<div aria-labelledby="online-calendar-tab" class="tab-pane" id="calendar-tab"
role="tabpanel">
<div class="row">
@ -1509,6 +1519,22 @@
}
});
jsonRequest("../v1/graph?type=hourlyUniqueAndNew&server=${serverUUID}", function (json, error) {
if (json) {
var uniquePlayers = {
name: s.name.uniquePlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.uniquePlayers, color: json.colors.playersOnline
};
var newPlayers = {
name: s.name.newPlayers, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.newPlayers, color: json.colors.newPlayers
};
dayByDay('hourlyUniqueChart', [uniquePlayers, newPlayers]);
} else if (error) {
$('#uniqueChart').text("Failed to load graph data: " + error)
}
});
jsonRequest("../v1/graph?type=serverCalendar&server=${serverUUID}", function (json, error) {
if (json) {
$('#calendar').text('');
@ -1573,7 +1599,6 @@
}
function setLoadingText(text) {
$('.loader-text').text(text);
}