Reduced the performance graph size

Reduced resolution of data:
- Last 30 days: Full resolution (1 per minute)
- Last 60 - 30 days: 1 per 5 minutes
- 60+ days old: 1 per 20 minutes
Effect:
- Reduced /v1/graphs?type=performance size from 21 MB to 9.15 MB (126k rows in database)

Added new endpoint /v1/graphs?type=optimizedPerformance that doesn't parse series separately
- Sends a single array of arrays instead of one array for each series
  - Added a parseDataSeries to graphs.js that translates the data
Effect:
- Reduced from 9.15 MB to 3.35 MB
- Moved some workload to the browser

Affects issues:
- Fixed #1622
This commit is contained in:
Risto Lahtela 2021-01-24 17:40:30 +02:00
parent f14dfe7a7c
commit ccd492c052
8 changed files with 173 additions and 36 deletions

View File

@ -249,4 +249,29 @@ public class TPSMutator {
tpsData.sort(new TPSComparator());
return Optional.of(tpsData.get(tpsData.size() - 1));
}
public Number[][] toArrays(boolean displayGaps) {
List<Number[]> arrays = new ArrayList<>();
Long lastX = null;
for (TPS tps : tpsData) {
long date = tps.getDate();
if (displayGaps && lastX != null && date - lastX > TimeUnit.MINUTES.toMillis(3L)) {
addMissingPoints(arrays, lastX, date);
}
lastX = date;
arrays.add(tps.toArray());
}
return arrays.toArray(new Number[0][]);
}
private void addMissingPoints(List<Number[]> arrays, Long lastX, long date) {
long iterate = lastX + TimeUnit.MINUTES.toMillis(1L);
while (iterate < date) {
Number[] entry = new Number[7];
entry[0] = iterate;
arrays.add(entry);
iterate += TimeUnit.MINUTES.toMillis(30L);
}
}
}

View File

@ -80,10 +80,7 @@ public class GraphJSONCreator {
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(halfYearAgo, now);
TPSMutator tpsMutator = new TPSMutator(db.query(TPSQueries.fetchTPSDataOfServer(serverUUID)));
return '{' +
"\"playersOnline\":" + lineGraphs.playersOnlineGraph(tpsMutator).toHighChartsSeries() +
",\"tps\":" + lineGraphs.tpsGraph(tpsMutator).toHighChartsSeries() +
@ -109,6 +106,33 @@ public class GraphJSONCreator {
"}}";
}
public Map<String, Object> optimizedPerformanceGraphJSON(UUID serverUUID) {
Database db = dbSystem.getDatabase();
TPSMutator tpsMutator = new TPSMutator(db.query(TPSQueries.fetchTPSDataOfServer(serverUUID)));
Number[][] values = tpsMutator.toArrays(config.isTrue(DisplaySettings.GAPS_IN_GRAPH_DATA));
return Maps.builder(String.class, Object.class)
.put("keys", new String[]{"date", "playersOnline", "tps", "cpu", "ram", "entities", "chunks", "disk"})
.put("values", values)
.put("colors", Maps.builder(String.class, Object.class)
.put("playersOnline", theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE))
.put("cpu", theme.getValue(ThemeVal.GRAPH_CPU))
.put("ram", theme.getValue(ThemeVal.GRAPH_RAM))
.put("entities", theme.getValue(ThemeVal.GRAPH_ENTITIES))
.put("chunks", theme.getValue(ThemeVal.GRAPH_CHUNKS))
.put("low", theme.getValue(ThemeVal.GRAPH_TPS_LOW))
.put("med", theme.getValue(ThemeVal.GRAPH_TPS_MED))
.put("high", theme.getValue(ThemeVal.GRAPH_TPS_HIGH))
.build())
.put("zones", Maps.builder(String.class, Object.class)
.put("tpsThresholdMed", config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED))
.put("tpsThresholdHigh", config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_HIGH))
.put("diskThresholdMed", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_MED))
.put("diskThresholdHigh", config.get(DisplaySettings.GRAPH_DISK_THRESHOLD_HIGH))
.build())
.build();
}
public String playersOnlineGraph(UUID serverUUID) {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();

View File

@ -30,6 +30,7 @@ public enum DataID {
KILLS,
PING_TABLE,
GRAPH_PERFORMANCE,
GRAPH_OPTIMIZED_PERFORMANCE,
GRAPH_ONLINE,
GRAPH_UNIQUE_NEW,
GRAPH_HOURLY_UNIQUE_NEW,

View File

@ -88,6 +88,8 @@ public class GraphsJSONResolver implements Resolver {
switch (type) {
case "performance":
return DataID.GRAPH_PERFORMANCE;
case "optimizedPerformance":
return DataID.GRAPH_OPTIMIZED_PERFORMANCE;
case "playersOnline":
return DataID.GRAPH_ONLINE;
case "uniqueAndNew":
@ -117,6 +119,8 @@ public class GraphsJSONResolver implements Resolver {
switch (id) {
case GRAPH_PERFORMANCE:
return graphJSON.performanceGraphJSON(serverUUID);
case GRAPH_OPTIMIZED_PERFORMANCE:
return graphJSON.optimizedPerformanceGraphJSON(serverUUID);
case GRAPH_ONLINE:
return graphJSON.playersOnlineGraph(serverUUID);
case GRAPH_UNIQUE_NEW:

View File

@ -168,4 +168,17 @@ public class TPS implements DateHolder {
"chunksLoaded=" + chunksLoaded + ", " +
"freeDiskSpace=" + freeDiskSpace + '}';
}
public Number[] toArray() {
return new Number[]{
getDate(),
getPlayers(),
getTicksPerSecond(),
getCPUUsage(),
getUsedMemory(),
getEntityCount(),
getChunksLoaded(),
getFreeDiskSpace()
};
}
}

View File

@ -29,6 +29,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
import static com.djrapitops.plan.storage.database.sql.tables.TPSTable.*;
@ -45,27 +46,72 @@ public class TPSQueries {
}
public static Query<List<TPS>> fetchTPSDataOfServer(UUID serverUUID) {
String sql = Select.all(TABLE_NAME)
.where(SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID)
.toString();
return db -> {
String selectLowestResolution = SELECT +
"MIN(t." + DATE + ") as " + DATE + ',' +
"MIN(t." + TPS + ") as " + TPS + ',' +
"MAX(t." + PLAYERS_ONLINE + ") as " + PLAYERS_ONLINE + ',' +
"MAX(t." + RAM_USAGE + ") as " + RAM_USAGE + ',' +
"MAX(t." + CPU_USAGE + ") as " + CPU_USAGE + ',' +
"MAX(t." + ENTITIES + ") as " + ENTITIES + ',' +
"MAX(t." + CHUNKS + ") as " + CHUNKS + ',' +
"MAX(t." + FREE_DISK + ") as " + FREE_DISK +
FROM + TABLE_NAME + " t" +
WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID +
AND + DATE + "<?" +
GROUP_BY + "FLOOR(" + DATE + "/?)";
String selectLowerResolution = SELECT +
"MIN(t." + DATE + ") as " + DATE + ',' +
"MIN(t." + TPS + ") as " + TPS + ',' +
"MAX(t." + PLAYERS_ONLINE + ") as " + PLAYERS_ONLINE + ',' +
"MAX(t." + RAM_USAGE + ") as " + RAM_USAGE + ',' +
"MAX(t." + CPU_USAGE + ") as " + CPU_USAGE + ',' +
"MAX(t." + ENTITIES + ") as " + ENTITIES + ',' +
"MAX(t." + CHUNKS + ") as " + CHUNKS + ',' +
"MAX(t." + FREE_DISK + ") as " + FREE_DISK +
FROM + TABLE_NAME + " t" +
WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID +
AND + DATE + ">=?" +
AND + DATE + "<?" +
GROUP_BY + "FLOOR(" + DATE + "/?)";
String selectNormalResolution = SELECT +
DATE + ',' + TPS + ',' + PLAYERS_ONLINE + ',' +
RAM_USAGE + ',' + CPU_USAGE + ',' + ENTITIES + ',' + CHUNKS + ',' + FREE_DISK +
FROM + TABLE_NAME +
WHERE + SERVER_ID + "=" + ServerTable.STATEMENT_SELECT_SERVER_ID +
AND + DATE + ">=?";
return new QueryStatement<List<TPS>>(sql, 50000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
}
String sql = selectLowestResolution +
UNION + selectLowerResolution +
UNION + selectNormalResolution +
ORDER_BY + DATE;
@Override
public List<TPS> processResults(ResultSet set) throws SQLException {
List<TPS> data = new ArrayList<>();
while (set.next()) {
com.djrapitops.plan.gathering.domain.TPS tps = extractTPS(set);
data.add(tps);
return db.query(new QueryStatement<List<TPS>>(sql, 50000) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
long now = System.currentTimeMillis();
long lowestResolution = TimeUnit.MINUTES.toMillis(20);
long lowResolution = TimeUnit.MINUTES.toMillis(5);
statement.setString(1, serverUUID.toString());
statement.setLong(2, now - TimeUnit.DAYS.toMillis(60));
statement.setLong(3, lowestResolution);
statement.setString(4, serverUUID.toString());
statement.setLong(5, now - TimeUnit.DAYS.toMillis(60));
statement.setLong(6, now - TimeUnit.DAYS.toMillis(30));
statement.setLong(7, lowResolution);
statement.setString(8, serverUUID.toString());
statement.setLong(9, now - TimeUnit.DAYS.toMillis(30));
}
return data;
}
@Override
public List<TPS> processResults(ResultSet set) throws SQLException {
List<TPS> data = new ArrayList<>();
while (set.next()) {
data.add(extractTPS(set));
}
return data;
}
});
};
}

View File

@ -182,6 +182,29 @@ function onlineActivityCalendar(id, event_data, firstDay) {
window.calendars.online_activity.render();
}
function mapToDataSeries(performanceData) {
const dataSeries = {
playersOnline: [],
tps: [],
cpu: [],
ram: [],
entities: [],
chunks: [],
disk: []
};
for (const entry of performanceData) {
const date = entry[0];
dataSeries.playersOnline.push([date, entry[1]]);
dataSeries.tps.push([date, entry[2]]);
dataSeries.cpu.push([date, entry[3]]);
dataSeries.ram.push([date, entry[4]]);
dataSeries.entities.push([date, entry[5]]);
dataSeries.chunks.push([date, entry[6]]);
dataSeries.disk.push([date, entry[7]]);
}
return dataSeries;
}
function performanceChart(id, playersOnlineSeries, tpsSeries, cpuSeries, ramSeries, entitySeries, chunkSeries) {
graphs.push(Highcharts.stockChart(id, {
rangeSelector: {

View File

@ -1330,9 +1330,9 @@
}
};
jsonRequest("../v1/graph?type=performance&server=${serverUUID}", function (json, error) {
jsonRequest("../v1/graph?type=optimizedPerformance&server=${serverUUID}", function (json, error) {
if (json) {
var zones = {
const zones = {
tps: [{
value: json.zones.tpsThresholdMed,
color: json.colors.low
@ -1354,51 +1354,52 @@
color: json.colors.high
}]
};
var series = {
const dataSeries = mapToDataSeries(json.values);
const series = {
playersOnline: {
name: s.name.playersOnline, type: s.type.areaSpline, tooltip: s.tooltip.zeroDecimals,
data: json.playersOnline, color: json.colors.playersOnline, yAxis: 0
data: dataSeries.playersOnline, color: json.colors.playersOnline, yAxis: 0
},
tps: {
name: s.name.tps, type: s.type.spline, color: json.colors.high,
zones: zones.tps, tooltip: s.tooltip.twoDecimals, data: json.tps,
zones: zones.tps, tooltip: s.tooltip.twoDecimals, data: dataSeries.tps,
yAxis: 1
},
cpu: {
name: s.name.cpu, type: s.type.spline, tooltip: s.tooltip.twoDecimals,
data: json.cpu, color: json.colors.cpu, yAxis: 2
data: dataSeries.cpu, color: json.colors.cpu, yAxis: 2
},
cpu_alt: {
name: s.name.cpu, type: s.type.spline, tooltip: s.tooltip.twoDecimals,
data: json.cpu, color: json.colors.cpu, yAxis: 1
data: dataSeries.cpu, color: json.colors.cpu, yAxis: 1
},
ram: {
name: s.name.ram, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.ram, color: json.colors.ram, yAxis: 3
data: dataSeries.ram, color: json.colors.ram, yAxis: 3
},
ram_alt: {
name: s.name.ram, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.ram, color: json.colors.ram, yAxis: 2
data: dataSeries.ram, color: json.colors.ram, yAxis: 2
},
entities: {
name: s.name.entities, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.entities, color: json.colors.entities, yAxis: 4
data: dataSeries.entities, color: json.colors.entities, yAxis: 4
},
entities_alt: {
name: s.name.entities, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.entities, color: json.colors.entities, yAxis: 1
data: dataSeries.entities, color: json.colors.entities, yAxis: 1
},
chunks: {
name: s.name.chunks, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.chunks, color: json.colors.chunks, yAxis: 5
data: dataSeries.chunks, color: json.colors.chunks, yAxis: 5
},
chunks_alt: {
name: s.name.chunks, type: s.type.spline, tooltip: s.tooltip.zeroDecimals,
data: json.chunks, color: json.colors.chunks, yAxis: 2
data: dataSeries.chunks, color: json.colors.chunks, yAxis: 2
},
disk: {
name: s.name.disk, type: s.type.spline, color: json.colors.high,
zones: zones.disk, tooltip: s.tooltip.zeroDecimals, data: json.disk
zones: zones.disk, tooltip: s.tooltip.zeroDecimals, data: dataSeries.disk
}
};
playersChart('playersOnlineChart', series.playersOnline, 2);