Created
March 17, 2025 13:26
-
-
Save Alemiz112/e911bacb621d57fc62b9cdf8b30ed3e0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2021 Alemiz | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package cubemc.waterdog.proxycore.network; | |
import com.google.gson.JsonObject; | |
import dev.waterdog.waterdogpe.ProxyServer; | |
import dev.waterdog.waterdogpe.event.EventPriority; | |
import dev.waterdog.waterdogpe.event.defaults.InitialServerConnectedEvent; | |
import dev.waterdog.waterdogpe.event.defaults.PlayerDisconnectedEvent; | |
import dev.waterdog.waterdogpe.event.defaults.TransferCompleteEvent; | |
import dev.waterdog.waterdogpe.network.NetworkMetrics; | |
import dev.waterdog.waterdogpe.network.PacketDirection; | |
import dev.waterdog.waterdogpe.network.connection.client.ClientConnection; | |
import dev.waterdog.waterdogpe.player.ProxiedPlayer; | |
import io.prometheus.client.Counter; | |
import io.prometheus.client.Gauge; | |
import io.prometheus.client.exporter.HTTPServer; | |
import it.unimi.dsi.fastutil.objects.ObjectArraySet; | |
import it.unimi.dsi.fastutil.objects.ObjectSet; | |
import it.unimi.dsi.fastutil.objects.ObjectSets; | |
import org.cloudburstmc.netty.channel.raknet.config.RakChannelMetrics; | |
import org.cloudburstmc.netty.channel.raknet.config.RakServerMetrics; | |
import java.io.IOException; | |
import java.net.InetSocketAddress; | |
public class CubeNetworkMetrics implements NetworkMetrics, RakServerMetrics, RakChannelMetrics { | |
// RakNet metrics | |
private static final Counter bytesIn = Counter.build() | |
.name("rak_bytes_in") | |
.help("RakNet inbound bytes") | |
.register(); | |
private static final Counter bytesOut = Counter.build() | |
.name("rak_bytes_out") | |
.help("RakNet outbound bytes") | |
.register(); | |
private static final Counter datagramsIn = Counter.build() | |
.name("rak_datagrams_in") | |
.help("RakNet inbound datagrams") | |
.register(); | |
private static final Counter datagramsOut = Counter.build() | |
.name("rak_datagrams_out") | |
.help("RakNet outbound datagrams") | |
.register(); | |
private static final Counter stateDatagrams = Counter.build() | |
.name("rak_stale_datagrams") | |
.help("RakNet sent stale datagrams counter") | |
.register(); | |
private static final Counter ackIn = Counter.build() | |
.name("rak_ack_in") | |
.help("RakNet acknowledge inbound") | |
.register(); | |
private static final Counter ackOut = Counter.build() | |
.name("rak_ack_out") | |
.help("RakNet acknowledge outbound") | |
.register(); | |
private static final Counter nackIn = Counter.build() | |
.name("rak_nack_in") | |
.help("RakNet nacknowledge inbound") | |
.register(); | |
private static final Counter nackOut = Counter.build() | |
.name("rak_nack_out") | |
.help("RakNet nacknowledge outbound") | |
.register(); | |
// Bedrock metrics | |
private static final Counter compressedBytes = Counter.build().name("waterdog_compressed_bytes") | |
.labelNames("direction") | |
.help("Compressed bytes") | |
.register(); | |
private static final Counter decompressedBytes = Counter.build().name("waterdog_decompressed_bytes") | |
.labelNames("direction") | |
.help("Decompressed bytes") | |
.register(); | |
private static final Counter passThroughBytes = Counter.build().name("waterdog_pass_thought_bytes") | |
.labelNames("direction") | |
.help("Passed thought bytes") | |
.register(); | |
private static final Counter encodedPackets = Counter.build().name("waterdog_encoded_packets") | |
.labelNames("direction") | |
.help("Encoded packets count") | |
.register(); | |
private static final Counter passThroughPackets = Counter.build().name("waterdog_pass_thought_packets") | |
.labelNames("direction") | |
.help("Passed thought packets count") | |
.register(); | |
// Player metrics | |
private static final Gauge playersCount = Gauge.build() | |
.name("waterdog_player_count") | |
.help("Waterdog player count") | |
.labelNames("server_address", "game_version") | |
.register(); | |
private static final Counter playerLogins = Counter.build() | |
.name("waterdog_player_logins") | |
.help("Waterdog player login attempts") | |
.register(); | |
private static final Counter playerTransfers = Counter.build() | |
.name("waterdog_player_transfer") | |
.help("Waterdog player transfers") | |
.register(); | |
private static final Gauge playerAveragePing = Gauge.build() | |
.name("waterdog_player_avg_ping") | |
.help("Average player ping in mc"). | |
register(); | |
private static final Gauge downstreamAveragePing = Gauge.build() | |
.name("waterdog_server_avg_ping") | |
.help("Average server ping in mc"). | |
register(); | |
private final NetworkManager loader; | |
private HTTPServer server; | |
private final ObjectSet<String> trackedPlayers = ObjectSets.synchronize(new ObjectArraySet<>()); | |
public CubeNetworkMetrics(NetworkManager loader, int port) { | |
this.loader = loader; | |
try { | |
this.server = new HTTPServer(port); | |
} catch (IOException e) { | |
this.loader.getLogger().error("Failed to start HTTP Prometheus metrics exporter on port " + port); | |
return; | |
} | |
ProxyServer proxy = this.loader.getLoader().getProxy(); | |
proxy.getEventManager().subscribe(InitialServerConnectedEvent.class, this::onPostLogin, EventPriority.HIGH); | |
proxy.getEventManager().subscribe(PlayerDisconnectedEvent.class, this::onDisconnect, EventPriority.HIGH); | |
proxy.getEventManager().subscribe(TransferCompleteEvent.class, this::onTransfer); | |
proxy.getScheduler().scheduleRepeating(this::calculateAvgPing, 20); | |
} | |
public void shutdown() { | |
if (this.server != null) { | |
this.server.close(); | |
} | |
} | |
private String getPlayerServerAddress(ProxiedPlayer player) { | |
JsonObject clientData = player.getLoginData().getClientData(); | |
if (clientData.has("ServerAddress")) { | |
return clientData.get("ServerAddress").getAsString().split(":")[0].toLowerCase(); | |
} | |
return "cubedmc.eu"; | |
} | |
private String getPlayerVersion(ProxiedPlayer player) { | |
return player.getLoginData().getClientData().has("GameVersion") ? | |
player.getLoginData().getClientData().get("GameVersion").getAsString() : | |
player.getProtocol().getMinecraftVersion(); | |
} | |
public void onLoginAttempt(InetSocketAddress address) { | |
playerLogins.inc(); | |
} | |
private void onPostLogin(InitialServerConnectedEvent event) { | |
String address = this.getPlayerServerAddress(event.getPlayer()); | |
String version = this.getPlayerVersion(event.getPlayer()); | |
this.trackedPlayers.add(event.getPlayer().getName()); | |
playersCount.labels(address, version).inc(); | |
} | |
private void onDisconnect(PlayerDisconnectedEvent event) { | |
if (this.trackedPlayers.remove(event.getPlayer().getName())) { | |
String address = this.getPlayerServerAddress(event.getPlayer()); | |
String version = this.getPlayerVersion(event.getPlayer()); | |
playersCount.labels(address, version).dec(); | |
} | |
} | |
private void onTransfer(TransferCompleteEvent event) { | |
playerTransfers.inc(); | |
} | |
private void calculateAvgPing() { | |
ProxyServer proxy = this.loader.getLoader().getProxy(); | |
int count = proxy.getPlayerManager().getPlayerCount(); | |
int upstreamAvg = 0; | |
int downstreamAvg = 0; | |
for (ProxiedPlayer player : proxy.getPlayers().values()) { | |
upstreamAvg += player.getPing(); | |
ClientConnection connection = player.getDownstreamConnection(); | |
if (connection != null && connection.isConnected()) { | |
downstreamAvg += connection.getPing(); | |
} | |
} | |
playerAveragePing.set((double) upstreamAvg / count); | |
downstreamAveragePing.set((double) downstreamAvg / count); | |
} | |
@Override | |
public void bytesIn(int count) { | |
bytesIn.inc(count); | |
} | |
@Override | |
public void bytesOut(int count) { | |
bytesOut.inc(count); | |
} | |
@Override | |
public void rakDatagramsIn(int count) { | |
datagramsIn.inc(count); | |
} | |
@Override | |
public void rakDatagramsOut(int count) { | |
datagramsOut.inc(count); | |
} | |
@Override | |
public void rakStaleDatagrams(int count) { | |
stateDatagrams.inc(count); | |
} | |
@Override | |
public void ackIn(int count) { | |
ackIn.inc(count); | |
} | |
@Override | |
public void ackOut(int count) { | |
ackOut.inc(count); | |
} | |
@Override | |
public void nackIn(int count) { | |
nackIn.inc(count); | |
} | |
@Override | |
public void nackOut(int count) { | |
nackOut.inc(count); | |
} | |
@Override | |
public void decompressedBytes(int count, PacketDirection direction) { | |
decompressedBytes.labels(direction.name()).inc(count); | |
} | |
@Override | |
public void compressedBytes(int count, PacketDirection direction) { | |
compressedBytes.labels(direction.name()).inc(count); | |
} | |
@Override | |
public void passedThroughBytes(int count, PacketDirection direction) { | |
passThroughBytes.labels(direction.name()).inc(count); | |
} | |
@Override | |
public void encodedPackets(int count, PacketDirection direction) { | |
encodedPackets.labels(direction.name()).inc(count); | |
} | |
@Override | |
public void passedThroughPackets(int count, PacketDirection direction) { | |
passThroughPackets.labels(direction.name()).inc(count); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment