-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FEAT: Add Operation throughput and latency metrics by mbean.
- Loading branch information
Showing
9 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
55 changes: 55 additions & 0 deletions
55
src/main/java/net/spy/memcached/metrics/LatencyMetricsSnapShot.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package net.spy.memcached.metrics; | ||
|
||
class LatencyMetricsSnapShot { | ||
private static final LatencyMetricsSnapShot EMPTY = new LatencyMetricsSnapShot(0, 0, 0, 0, 0, 0); | ||
|
||
private final long avgLatency; | ||
private final long minLatency; | ||
private final long maxLatency; | ||
private final long p25Latency; | ||
private final long p50Latency; | ||
private final long p75Latency; | ||
private final long timestamp; // 캐시 생성 시간 | ||
|
||
LatencyMetricsSnapShot(long avg, long min, long max, long p25, long p50, long p75) { | ||
this.avgLatency = avg; | ||
this.minLatency = min; | ||
this.maxLatency = max; | ||
this.p25Latency = p25; | ||
this.p50Latency = p50; | ||
this.p75Latency = p75; | ||
this.timestamp = System.currentTimeMillis(); | ||
} | ||
|
||
public static LatencyMetricsSnapShot empty() { | ||
return EMPTY; | ||
} | ||
|
||
public long getAvgLatency() { | ||
return avgLatency; | ||
} | ||
|
||
public long getMinLatency() { | ||
return minLatency; | ||
} | ||
|
||
public long getMaxLatency() { | ||
return maxLatency; | ||
} | ||
|
||
public long getP25Latency() { | ||
return p25Latency; | ||
} | ||
|
||
public long getP50Latency() { | ||
return p50Latency; | ||
} | ||
|
||
public long getP75Latency() { | ||
return p75Latency; | ||
} | ||
|
||
public long getTimestamp() { | ||
return timestamp; | ||
} | ||
} |
149 changes: 149 additions & 0 deletions
149
src/main/java/net/spy/memcached/metrics/OpLatencyMonitor.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package net.spy.memcached.metrics; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.concurrent.atomic.AtomicReferenceArray; | ||
|
||
import net.spy.memcached.ArcusMBeanServer; | ||
|
||
public final class OpLatencyMonitor implements OpLatencyMonitorMBean { | ||
|
||
private static final OpLatencyMonitor INSTANCE = new OpLatencyMonitor(); | ||
private static final long CACHE_DURATION = 2000; // 2초 캐시 | ||
private static final int WINDOW_SIZE = 10_000; | ||
|
||
private final AtomicReferenceArray<Long> latencies = new AtomicReferenceArray<>(WINDOW_SIZE); | ||
private final AtomicInteger currentIndex = new AtomicInteger(0); | ||
private final AtomicInteger count = new AtomicInteger(0); | ||
private final AtomicReference<LatencyMetricsSnapShot> cachedMetrics | ||
= new AtomicReference<>(LatencyMetricsSnapShot.empty()); | ||
private final boolean enabled; | ||
|
||
private OpLatencyMonitor() { | ||
if (System.getProperty("arcus.mbean", "false").toLowerCase().equals("false")) { | ||
enabled = false; | ||
return; | ||
} | ||
enabled = true; | ||
for (int i = 0; i < WINDOW_SIZE; i++) { | ||
latencies.set(i, 0L); | ||
} | ||
|
||
try { | ||
ArcusMBeanServer mbs = ArcusMBeanServer.getInstance(); | ||
mbs.registMBean(this, this.getClass().getPackage().getName() | ||
+ ":type=" + this.getClass().getSimpleName()); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to register MBean", e); | ||
} | ||
} | ||
|
||
public static OpLatencyMonitor getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
public void recordLatency(long startNanos) { | ||
if (!enabled) { | ||
return; | ||
} | ||
long latencyMicros = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - startNanos); | ||
int index = currentIndex.getAndUpdate(i -> (i + 1) % WINDOW_SIZE); | ||
latencies.lazySet(index, latencyMicros); | ||
|
||
if (count.get() < WINDOW_SIZE) { | ||
count.incrementAndGet(); | ||
} | ||
} | ||
|
||
// 모든 메트릭을 한 번에 계산하고 캐시하는 메서드 | ||
private LatencyMetricsSnapShot computeMetrics() { | ||
int currentCount = count.get(); | ||
if (currentCount == 0) { | ||
return LatencyMetricsSnapShot.empty(); | ||
} | ||
|
||
// 현재 데이터를 배열로 복사 | ||
List<Long> sortedLatencies = new ArrayList<>(currentCount); | ||
int startIndex = currentIndex.get(); | ||
|
||
for (int i = 0; i < currentCount; i++) { | ||
int idx = (startIndex - i + WINDOW_SIZE) % WINDOW_SIZE; | ||
long value = latencies.get(idx); | ||
if (value > 0) { | ||
sortedLatencies.add(value); | ||
} | ||
} | ||
|
||
if (sortedLatencies.isEmpty()) { | ||
return LatencyMetricsSnapShot.empty(); | ||
} | ||
|
||
sortedLatencies.sort(Long::compareTo); | ||
|
||
// 모든 메트릭을 한 번에 계산 | ||
long avg = sortedLatencies.stream().mapToLong(Long::longValue).sum() / sortedLatencies.size(); | ||
long min = sortedLatencies.get(0); | ||
long max = sortedLatencies.get(sortedLatencies.size() - 1); | ||
long p25 = sortedLatencies.get((int) Math.ceil((sortedLatencies.size() * 25.0) / 100.0) - 1); | ||
long p50 = sortedLatencies.get((int) Math.ceil((sortedLatencies.size() * 50.0) / 100.0) - 1); | ||
long p75 = sortedLatencies.get((int) Math.ceil((sortedLatencies.size() * 75.0) / 100.0) - 1); | ||
|
||
return new LatencyMetricsSnapShot(avg, min, max, p25, p50, p75); | ||
} | ||
|
||
// 캐시된 메트릭을 가져오거나 필요시 새로 계산 | ||
private LatencyMetricsSnapShot getMetricsSnapshot() { | ||
LatencyMetricsSnapShot current = cachedMetrics.get(); | ||
long now = System.currentTimeMillis(); | ||
|
||
// 캐시가 유효한지 확인 | ||
if (now - current.getTimestamp() < CACHE_DURATION) { | ||
return current; | ||
} | ||
|
||
// 새로운 메트릭 계산 및 캐시 업데이트 | ||
LatencyMetricsSnapShot newMetrics = computeMetrics(); | ||
cachedMetrics.set(newMetrics); | ||
return newMetrics; | ||
} | ||
|
||
@Override | ||
public long getAverageLatencyMicros() { | ||
return getMetricsSnapshot().getAvgLatency(); | ||
} | ||
|
||
@Override | ||
public long getMinLatencyMicros() { | ||
return getMetricsSnapshot().getMinLatency(); | ||
} | ||
|
||
@Override | ||
public long getMaxLatencyMicros() { | ||
return getMetricsSnapshot().getMaxLatency(); | ||
} | ||
|
||
@Override | ||
public long get25thPercentileLatencyMicros() { | ||
return getMetricsSnapshot().getP25Latency(); | ||
} | ||
|
||
@Override | ||
public long get50thPercentileLatencyMicros() { | ||
return getMetricsSnapshot().getP50Latency(); | ||
} | ||
|
||
@Override | ||
public long get75thPercentileLatencyMicros() { | ||
return getMetricsSnapshot().getP75Latency(); | ||
} | ||
|
||
@Override | ||
public void resetStatistics() { | ||
count.set(0); | ||
currentIndex.set(0); | ||
cachedMetrics.set(LatencyMetricsSnapShot.empty()); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/net/spy/memcached/metrics/OpLatencyMonitorMBean.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package net.spy.memcached.metrics; | ||
|
||
public interface OpLatencyMonitorMBean { | ||
long getAverageLatencyMicros(); | ||
|
||
long getMaxLatencyMicros(); | ||
|
||
long getMinLatencyMicros(); | ||
|
||
long get25thPercentileLatencyMicros(); | ||
|
||
long get50thPercentileLatencyMicros(); | ||
|
||
long get75thPercentileLatencyMicros(); | ||
|
||
void resetStatistics(); | ||
} |
90 changes: 90 additions & 0 deletions
90
src/main/java/net/spy/memcached/metrics/OpThroughputMonitor.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package net.spy.memcached.metrics; | ||
|
||
import java.util.concurrent.atomic.AtomicLong; | ||
import java.util.concurrent.atomic.LongAdder; | ||
|
||
import net.spy.memcached.ArcusMBeanServer; | ||
|
||
public final class OpThroughputMonitor implements OpThroughputMonitorMBean { | ||
private static final OpThroughputMonitor INSTANCE = new OpThroughputMonitor(); | ||
|
||
private final LongAdder completeOps = new LongAdder(); | ||
private final LongAdder cancelOps = new LongAdder(); | ||
private final LongAdder timeOutOps = new LongAdder(); | ||
private final AtomicLong lastResetTime = new AtomicLong(System.currentTimeMillis()); | ||
private final boolean enabled; | ||
|
||
private OpThroughputMonitor() { | ||
if (System.getProperty("arcus.mbean", "false").toLowerCase().equals("false")) { | ||
enabled = false; | ||
return; | ||
} | ||
enabled = true; | ||
try { | ||
ArcusMBeanServer mbs = ArcusMBeanServer.getInstance(); | ||
mbs.registMBean(this, this.getClass().getPackage().getName() | ||
+ ":type=" + this.getClass().getSimpleName()); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to register Throughput MBean", e); | ||
} | ||
} | ||
|
||
public static OpThroughputMonitor getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
public void addCompletedOpCount() { | ||
if (!enabled) { | ||
return; | ||
} | ||
completeOps.increment(); | ||
} | ||
|
||
public void addCanceledOpCount() { | ||
if (!enabled) { | ||
return; | ||
} | ||
cancelOps.increment(); | ||
} | ||
|
||
public void addTimeOutedOpCount(int count) { | ||
if (!enabled) { | ||
return; | ||
} | ||
timeOutOps.add(count); | ||
} | ||
|
||
@Override | ||
public long getCompletedOps() { | ||
return getThroughput(completeOps); | ||
} | ||
|
||
@Override | ||
public long getCanceledOps() { | ||
return getThroughput(cancelOps); | ||
} | ||
|
||
@Override | ||
public long getTimeoutOps() { | ||
return getThroughput(timeOutOps); | ||
} | ||
|
||
@Override | ||
public void resetStatistics() { | ||
completeOps.reset(); | ||
cancelOps.reset(); | ||
timeOutOps.reset(); | ||
lastResetTime.set(System.currentTimeMillis()); | ||
} | ||
|
||
private long getThroughput(LongAdder ops) { | ||
long currentTime = System.currentTimeMillis(); | ||
long lastTime = lastResetTime.get(); | ||
long countValue = ops.sum(); | ||
|
||
// 경과 시간 계산 (초 단위) | ||
long elapsedSeconds = (long) ((currentTime - lastTime) / 1000.0); | ||
|
||
return countValue / elapsedSeconds; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/net/spy/memcached/metrics/OpThroughputMonitorMBean.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package net.spy.memcached.metrics; | ||
|
||
public interface OpThroughputMonitorMBean { | ||
long getCompletedOps(); | ||
|
||
long getCanceledOps(); | ||
|
||
long getTimeoutOps(); | ||
|
||
void resetStatistics(); | ||
} |
This file contains 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
Oops, something went wrong.