package com.jivesoftware.os.amza.service.stats; import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Interner; import com.google.common.collect.Interners; import com.google.common.collect.Maps; import com.google.common.collect.Multiset; import com.jivesoftware.os.amza.api.IoStats; import com.jivesoftware.os.amza.api.partition.PartitionName; import com.jivesoftware.os.amza.api.ring.RingMember; import com.jivesoftware.os.amza.api.wal.WALCompactionStats; import com.jivesoftware.os.jive.utils.ordered.id.JiveEpochTimestampProvider; import com.jivesoftware.os.jive.utils.ordered.id.SnowflakeIdPacker; import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; /** * @author jonathan.colt */ public class AmzaStats { private static final SnowflakeIdPacker snowflakeIdPacker = new SnowflakeIdPacker(); private static final JiveEpochTimestampProvider jiveEpochTimestampProvider = new JiveEpochTimestampProvider(); private final Map<RingMember, LongAdder> took = new ConcurrentSkipListMap<>(); private final Map<CompactionFamily, Map<String, CompactionStats>> ongoingCompaction = Maps.newConcurrentMap(); private final Map<CompactionFamily, LongAdder> totalCompactions = Maps.newConcurrentMap(); private final List<Entry<String, CompactionStats>> recentCompaction = new ArrayList<>(); private final Interner<String> compactionNameInterner = Interners.newStrongInterner(); public final Map<RingMember, LongAdder> longPolled = new ConcurrentSkipListMap<>(); public final Map<RingMember, LongAdder> longPollAvailables = new ConcurrentSkipListMap<>(); private final Totals grandTotals = new Totals(); private final Map<PartitionName, Totals> partitionTotals = Maps.newConcurrentMap(); public final Multiset<RingMember> takeErrors = ConcurrentHashMultiset.create(); public final IoStats loadIoStats = new IoStats(); public final IoStats getIoStats = new IoStats(); public final IoStats takeIoStats = new IoStats(); public final IoStats mergeIoStats = new IoStats(); public final IoStats updateIoStats = new IoStats(); public final IoStats compactTombstoneIoStats = new IoStats(); public final NetStats netStats = new NetStats(); public final LongAdder addMember = new LongAdder(); public final LongAdder removeMember = new LongAdder(); public final LongAdder getRing = new LongAdder(); public final LongAdder rowsStream = new LongAdder(); public final LongAdder completedRowsStream = new LongAdder(); public final LongAdder availableRowsStream = new LongAdder(); public final LongAdder rowsTaken = new LongAdder(); public final LongAdder completedRowsTake = new LongAdder(); public final LongAdder pingsSent = new LongAdder(); public final LongAdder pingsReceived = new LongAdder(); public final LongAdder pongsSent = new LongAdder(); public final LongAdder pongsReceived = new LongAdder(); public final LongAdder invalidatesSent = new LongAdder(); public final LongAdder invalidatesReceived = new LongAdder(); public final LongAdder backPressure = new LongAdder(); public final LongAdder pushBacks = new LongAdder(); public long[] deltaStripeMergeLoaded = new long[0]; public double[] deltaStripeLoad = new double[0]; public long[] deltaStripeMergePending = new long[0]; public double[] deltaStripeMerge = new double[0]; public final LongAdder deltaFirstCheckRemoves = new LongAdder(); public final LongAdder deltaSecondCheckRemoves = new LongAdder(); public final LongAdder takes = new LongAdder(); public final LongAdder takeExcessRows = new LongAdder(); public AmzaStats() { } public void deltaStripeLoad(int index, long count, double load) { long[] copyCount = deltaStripeMergeLoaded; double[] copy = deltaStripeLoad; if (index >= copy.length) { double[] newArray = new double[index + 1]; System.arraycopy(copy, 0, newArray, 0, copy.length); copy = newArray; deltaStripeLoad = copy; } if (index >= copyCount.length) { long[] newArrayCount = new long[index + 1]; System.arraycopy(copyCount, 0, newArrayCount, 0, copyCount.length); copyCount = newArrayCount; deltaStripeMergeLoaded = copyCount; } copyCount[index] = count; copy[index] = load; } public void deltaStripeMerge(int index, long count, double load) { long[] copyCount = deltaStripeMergePending; double[] copy = deltaStripeMerge; if (index >= copy.length) { long[] newArrayCount = new long[index + 1]; double[] newArray = new double[index + 1]; System.arraycopy(copyCount, 0, newArrayCount, 0, copyCount.length); System.arraycopy(copy, 0, newArray, 0, copy.length); copyCount = newArrayCount; copy = newArray; deltaStripeMergePending = copyCount; deltaStripeMerge = copy; } copyCount[index] = count; copy[index] = load; } public long[] highwaterFlushed = new long[0]; public long[] highwaterPending = new long[0]; public double[] highwaterPendingLoad = new double[0]; public void highwater(int index, long flushed, long pending, double load) { long[] copyFlushed = highwaterFlushed; long[] copyPending = highwaterPending; double[] copyLoad = highwaterPendingLoad; if (index >= copyLoad.length) { long[] newArrayFlushed = new long[index + 1]; long[] newArrayPending = new long[index + 1]; double[] newArray = new double[index + 1]; System.arraycopy(copyFlushed, 0, newArrayFlushed, 0, copyFlushed.length); System.arraycopy(copyPending, 0, newArrayPending, 0, copyPending.length); System.arraycopy(copyLoad, 0, newArray, 0, copyLoad.length); copyFlushed = newArrayFlushed; copyPending = newArrayPending; copyLoad = newArray; highwaterFlushed = copyFlushed; highwaterPending = copyPending; highwaterPendingLoad = copyLoad; } if (flushed > 0) { copyFlushed[index] += flushed; } if (pending >= 0) { copyPending[index] = pending; } if (load >= 0) { copyLoad[index] = load; } } static public class Totals { public final LongAdder gets = new LongAdder(); public volatile long getsLatency = 0; public final LongAdder scans = new LongAdder(); public volatile long scansLatency = 0; public final LongAdder scanKeys = new LongAdder(); public volatile long scanKeysLatency = 0; public final LongAdder updates = new LongAdder(); public volatile long updatesLag = 0; public final LongAdder offers = new LongAdder(); public volatile long offersLag = 0; public final Map<RingMember, AtomicLong> memberOffersLag = Maps.newConcurrentMap(); public final LongAdder takes = new LongAdder(); public volatile long takesLag = 0; public final LongAdder takeApplies = new LongAdder(); public volatile long takeAppliesLag = 0; public final LongAdder directApplies = new LongAdder(); public volatile long directAppliesLag = 0; public final LongAdder acks = new LongAdder(); public volatile long acksLag = 0; public final Map<RingMember, AtomicLong> memberAcksLag = Maps.newConcurrentMap(); public final LongAdder quorums = new LongAdder(); public volatile long quorumsLatency = 0; public final Map<RingMember, AtomicLong> memberQuorumsLatency = Maps.newConcurrentMap(); public final LongAdder quorumTimeouts = new LongAdder(); } public void longPolled(RingMember member) { longPolled.computeIfAbsent(member, (key) -> new LongAdder()).increment(); } public void longPollAvailables(RingMember member) { longPollAvailables.computeIfAbsent(member, (key) -> new LongAdder()).increment(); } public void took(RingMember member) { took.computeIfAbsent(member, (key) -> new LongAdder()).increment(); } public long getTotalTakes(RingMember member) { LongAdder got = took.get(member); if (got == null) { return 0; } return got.longValue(); } public enum CompactionFamily { expunge, tombstone, merge, load; } public int ongoingCompaction(CompactionFamily family) { Map<String, CompactionStats> got = ongoingCompaction.computeIfAbsent(family, (key) -> Maps.newConcurrentMap()); return got.size(); } public CompactionStats beginCompaction(CompactionFamily family, String name) { Map<String, CompactionStats> got = ongoingCompaction.computeIfAbsent(family, (key) -> Maps.newConcurrentMap()); CompactionStats compactionStats = new CompactionStats(family, name, System.currentTimeMillis()); got.put(name, compactionStats); return compactionStats; } public class CompactionStats implements WALCompactionStats { private final CompactionFamily family; private final String name; private final long startTime; private long endTime; private final Map<String, Long> counters = Maps.newConcurrentMap(); private final Map<String, Long> timers = Maps.newConcurrentMap(); public CompactionStats(CompactionFamily family, String name, long startTime) { this.family = family; this.name = name; this.startTime = startTime; } public long startTime() { return startTime; } public void finished() { this.endTime = System.currentTimeMillis(); endCompaction(family, name); } public long elapse() { return System.currentTimeMillis() - startTime; } public long duration() { if (endTime == 0) { return elapse(); } return endTime - startTime; } @Override public Set<Map.Entry<String, Long>> getTimings() { return timers.entrySet(); } @Override public Set<Map.Entry<String, Long>> getCounts() { return counters.entrySet(); } @Override public void add(String name, long count) { counters.compute(compactionNameInterner.intern(name), (k, v) -> { return v == null ? count : v + count; }); } @Override public void start(String name) { timers.compute(compactionNameInterner.intern(name), (k, v) -> { return v == null ? System.currentTimeMillis() : v; }); } @Override public void stop(String name) { timers.compute(compactionNameInterner.intern(name), (k, v) -> { return v == null ? null : System.currentTimeMillis() - v; }); } } private void endCompaction(CompactionFamily family, String name) { Map<String, CompactionStats> got = ongoingCompaction.computeIfAbsent(family, (key) -> Maps.newConcurrentMap()); CompactionStats compactionStats = got.remove(name); totalCompactions.computeIfAbsent(family, (key) -> new LongAdder()).increment(); if (compactionStats != null) { compactionStats.finished(); recentCompaction.add(new AbstractMap.SimpleEntry<>(family + " " + name, compactionStats)); while (recentCompaction.size() > 1_000) { recentCompaction.remove(0); } } } public List<Entry<String, CompactionStats>> recentCompaction() { return recentCompaction; } public List<Entry<String, CompactionStats>> ongoingCompactions(CompactionFamily... families) { List<Entry<String, CompactionStats>> ongoing = new ArrayList<>(); for (CompactionFamily family : families) { Map<String, CompactionStats> got = ongoingCompaction.get(family); if (got != null) { for (Entry<String, CompactionStats> e : got.entrySet()) { ongoing.add(new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue())); } } } return ongoing; } public long getTotalCompactions(CompactionFamily family) { return totalCompactions.computeIfAbsent(family, (key) -> new LongAdder()).longValue(); } public Totals getGrandTotal() { return grandTotals; } public void updates(RingMember from, PartitionName partitionName, int count, long smallestTxId) { grandTotals.updates.add(count); Totals totals = partitionTotals(partitionName); totals.updates.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.updatesLag = lag; grandTotals.updatesLag = (grandTotals.updatesLag + lag) / 2; } } public void offers(RingMember to, PartitionName partitionName, int count, long smallestTxId) { grandTotals.offers.add(count); Totals totals = partitionTotals(partitionName); totals.offers.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.offersLag = lag; grandTotals.offersLag = (grandTotals.offersLag + lag) / 2; grandTotals.memberOffersLag.computeIfAbsent(to, ringMember1 -> new AtomicLong()) .accumulateAndGet(lag, (left, right) -> (left + right) / 2); } } public void acks(RingMember from, PartitionName partitionName, int count, long smallestTxId) { grandTotals.acks.add(count); Totals totals = partitionTotals(partitionName); totals.acks.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.acksLag = lag; grandTotals.acksLag = (grandTotals.acksLag + lag) / 2; grandTotals.memberAcksLag.computeIfAbsent(from, ringMember1 -> new AtomicLong()) .accumulateAndGet(lag, (left, right) -> (left + right) / 2); } } public void quorums(PartitionName partitionName, int count, long lag, List<RingMember> tookFrom) { grandTotals.quorums.add(count); Totals totals = partitionTotals(partitionName); totals.quorums.add(count); totals.quorumsLatency = lag; grandTotals.quorumsLatency = (grandTotals.quorumsLatency + lag) / 2; for (RingMember ringMember : tookFrom) { grandTotals.memberQuorumsLatency.computeIfAbsent(ringMember, ringMember1 -> new AtomicLong()) .accumulateAndGet(lag, (left, right) -> (left + right) / 2); } } public void quorumTimeouts(PartitionName partitionName, int count) { grandTotals.quorumTimeouts.add(count); Totals totals = partitionTotals(partitionName); totals.quorumTimeouts.add(count); } public void took(RingMember from, PartitionName partitionName, int count, long smallestTxId) { grandTotals.takes.add(count); Totals totals = partitionTotals(partitionName); totals.takes.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.takesLag = lag; grandTotals.takesLag = (grandTotals.takesLag + lag) / 2; } } public void tookApplied(RingMember from, PartitionName partitionName, int count, long smallestTxId) { grandTotals.takeApplies.add(count); Totals totals = partitionTotals(partitionName); totals.takeApplies.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.takeAppliesLag = lag; grandTotals.takeAppliesLag = (grandTotals.takeAppliesLag + lag) / 2; } } public void direct(PartitionName partitionName, int count, long smallestTxId) { grandTotals.directApplies.add(count); Totals totals = partitionTotals(partitionName); totals.directApplies.add(count); if (smallestTxId != -1) { long lag = lag(smallestTxId); totals.directAppliesLag = lag; grandTotals.directAppliesLag = (grandTotals.directAppliesLag + lag) / 2; } } public void gets(PartitionName partitionName, int count, long lag) { grandTotals.gets.add(count); Totals totals = partitionTotals(partitionName); totals.gets.add(count); totals.getsLatency = lag; grandTotals.getsLatency = (grandTotals.getsLatency + lag) / 2; } public void scans(PartitionName partitionName, int count, long lag) { grandTotals.scans.add(count); Totals totals = partitionTotals(partitionName); totals.scans.add(count); totals.scansLatency = lag; grandTotals.scansLatency = (grandTotals.scansLatency + lag) / 2; } public void scanKeys(PartitionName partitionName, int count, long lag) { grandTotals.scanKeys.add(count); Totals totals = partitionTotals(partitionName); totals.scanKeys.add(count); totals.scanKeysLatency = lag; grandTotals.scanKeysLatency = (grandTotals.scanKeysLatency + lag) / 2; } private Totals partitionTotals(PartitionName versionedPartitionName) { Totals got = partitionTotals.get(versionedPartitionName); if (got == null) { got = new Totals(); partitionTotals.put(versionedPartitionName, got); } return got; } public Map<PartitionName, Totals> getPartitionTotals() { return partitionTotals; } long lag(long oldest) { if (oldest != Long.MAX_VALUE) { long[] unpack = snowflakeIdPacker.unpack(oldest); long lag = jiveEpochTimestampProvider.getApproximateTimestamp(System.currentTimeMillis()) - unpack[0]; return lag; } return 0; } }