package net.openhft.chronicle.engine.server.internal; import net.openhft.chronicle.core.threads.EventHandler; import net.openhft.chronicle.core.threads.InvalidEventHandlerException; import net.openhft.chronicle.engine.api.map.MapView; import net.openhft.chronicle.engine.tree.ChronicleQueueView; import net.openhft.chronicle.network.NetworkStats; import net.openhft.chronicle.network.WireNetworkStats; import net.openhft.chronicle.queue.ExcerptTailer; import net.openhft.chronicle.queue.impl.RollingChronicleQueue; import net.openhft.chronicle.wire.DocumentContext; import net.openhft.chronicle.wire.ValueIn; import net.openhft.chronicle.wire.Wires; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static net.openhft.chronicle.wire.Wires.project; /** * populates a map containing a exponential moving average * * @author Rob Austin. */ public class NetworkStatsSummary implements EventHandler { private final long index; private double alpha = 1.0 / 60.0; @Nullable private final RollingChronicleQueue rollingChronicleQueue; @NotNull private final MapView<String, Stats> latestStatsPerClientId; @NotNull private ExcerptTailer tailer = null; public NetworkStatsSummary(@NotNull ChronicleQueueView qv, @NotNull MapView<String, Stats> latestStatsPerClientId) { rollingChronicleQueue = qv.chronicleQueue(); this.latestStatsPerClientId = latestStatsPerClientId; @NotNull final Collection<Stats> values = this.latestStatsPerClientId.values(); @NotNull final AtomicLong index = new AtomicLong(); values.forEach(v -> index.set(Math.max(index.get(), v.index))); this.index = index.get(); } private final NetworkStats ns = new WireNetworkStats(); @Override public boolean action() throws InvalidEventHandlerException, InterruptedException { try { if (tailer == null) { tailer = rollingChronicleQueue.createTailer(); if (index > 0) tailer.moveToIndex(index); tailer.readingDocument(false).close(); } try (DocumentContext documentContext = tailer.readingDocument(false)) { if (!documentContext.isPresent()) return false; StringBuilder sb = Wires.acquireStringBuilder(); @NotNull ValueIn valueIn = documentContext.wire().read(sb); if ("NetworkStats".contentEquals(sb)) { valueIn.marshallable(ns); final String userId = ns.userId(); if (userId != null && !userId.isEmpty()) { updateMap(ns, documentContext.index()); } } } return true; } catch (Throwable t) { t.printStackTrace(); return true; } } @NotNull private Stats stats0 = new Stats(); private void updateMap(@NotNull NetworkStats ns, final long index) { final String key = ns.userId(); @Nullable final Stats stats = latestStatsPerClientId.getUsing(key, stats0); if (stats == null) { Stats value = project(Stats.class, ns); value.writeEma = value.writeBps(); value.readEma = value.readBps(); value.index = index; latestStatsPerClientId.put(key, value); return; } double lastWriteEma = stats.writeEma; double lastReadEma = stats.readEma; if (equalsSecond(stats.timestamp(), ns.timestamp())) { long lastWriteBps = stats.writeBps(); long lastReadBps = stats.readBps(); Wires.copyTo(ns, stats); stats.writeBps(stats.writeBps() + lastWriteBps); stats.readBps(stats.readBps() + lastReadBps); } else Wires.copyTo(ns, stats); stats.writeEma((stats.writeBps() * (1 - alpha)) + (lastWriteEma * alpha)); stats.readEma((stats.readBps() * (1 - alpha)) + (lastReadEma * alpha)); latestStatsPerClientId.put(key, stats); } /** * @return true if they are in the same seconds */ private boolean equalsSecond(long t1, long t2) { return TimeUnit.MILLISECONDS.toSeconds(t1) == TimeUnit.MILLISECONDS.toSeconds(t2); } public static class Stats extends WireNetworkStats { double writeEma; double readEma; long index; @NotNull Stats writeEma(double writeEma) { this.writeEma = writeEma; return this; } @NotNull Stats readEma(double readEma) { this.readEma = readEma; return this; } } }