Back
MetricsCollectorImpl.java
javasrc/main/java/com/scriptslab/core/metrics/MetricsCollectorImpl.java
package com.scriptslab.core.metrics;
import com.scriptslab.api.metrics.MetricsCollector;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.DoubleAdder;
/**
* Thread-safe metrics collector implementation.
*/
public final class MetricsCollectorImpl implements MetricsCollector {
private final Map<String, DoubleAdder> metrics;
private final Map<String, AtomicLong> counters;
private final Map<String, TimingMetric> timings;
public MetricsCollectorImpl() {
this.metrics = new ConcurrentHashMap<>();
this.counters = new ConcurrentHashMap<>();
this.timings = new ConcurrentHashMap<>();
}
@Override
public void record(String name, double value) {
metrics.computeIfAbsent(name, k -> new DoubleAdder()).add(value);
}
@Override
public void increment(String name) {
increment(name, 1);
}
@Override
public void increment(String name, long amount) {
counters.computeIfAbsent(name, k -> new AtomicLong()).addAndGet(amount);
}
@Override
public void recordTiming(String name, long durationMs) {
timings.computeIfAbsent(name, k -> new TimingMetric()).record(durationMs);
}
@Override
public CompletableFuture<Double> getMetric(String name) {
return CompletableFuture.supplyAsync(() -> {
// Check metrics
DoubleAdder adder = metrics.get(name);
if (adder != null) {
return adder.sum();
}
// Check counters
AtomicLong counter = counters.get(name);
if (counter != null) {
return (double) counter.get();
}
// Check timings
TimingMetric timing = timings.get(name);
if (timing != null) {
return timing.getAverage();
}
return 0.0;
});
}
@Override
public CompletableFuture<Map<String, Double>> getAllMetrics() {
return CompletableFuture.supplyAsync(() -> {
Map<String, Double> result = new HashMap<>();
// Add all metrics
metrics.forEach((name, adder) -> result.put(name, adder.sum()));
// Add all counters
counters.forEach((name, counter) -> result.put(name, (double) counter.get()));
// Add all timings
timings.forEach((name, timing) -> {
result.put(name + ".avg", timing.getAverage());
result.put(name + ".min", (double) timing.getMin());
result.put(name + ".max", (double) timing.getMax());
result.put(name + ".count", (double) timing.getCount());
});
return result;
});
}
@Override
public void reset() {
metrics.clear();
counters.clear();
timings.clear();
}
@Override
public void reset(String name) {
metrics.remove(name);
counters.remove(name);
timings.remove(name);
}
/**
* Timing metric with min/max/avg tracking.
*/
private static class TimingMetric {
private final AtomicLong count = new AtomicLong();
private final AtomicLong total = new AtomicLong();
private final AtomicLong min = new AtomicLong(Long.MAX_VALUE);
private final AtomicLong max = new AtomicLong(Long.MIN_VALUE);
void record(long duration) {
count.incrementAndGet();
total.addAndGet(duration);
// Update min
long currentMin;
do {
currentMin = min.get();
if (duration >= currentMin) break;
} while (!min.compareAndSet(currentMin, duration));
// Update max
long currentMax;
do {
currentMax = max.get();
if (duration <= currentMax) break;
} while (!max.compareAndSet(currentMax, duration));
}
double getAverage() {
long c = count.get();
return c > 0 ? (double) total.get() / c : 0.0;
}
long getMin() {
long m = min.get();
return m == Long.MAX_VALUE ? 0 : m;
}
long getMax() {
long m = max.get();
return m == Long.MIN_VALUE ? 0 : m;
}
long getCount() {
return count.get();
}
}
}