ScriptsLab
WikiDownloadsSourcesSupport
ScriptsLab
DocumentationDownloadsGitHubDiscord

© 2026 ScriptsLab

Back to src/main/java/com/scriptslab/core/metrics
J

MetricsCollectorImpl.java

Java · 157 lines · 4.6 KB

src/main/java/com/scriptslab/core/metrics/MetricsCollectorImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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();
        }
    }
}