• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-query / 24774193462

22 Apr 2026 10:48AM UTC coverage: 59.317% (-0.9%) from 60.242%
24774193462

push

github

web-flow
Merge pull request #77 from knowledgepixels/perf/idle-poll-and-diagnostics

perf/feat: skip idle admin-repo writes + loader liveness diagnostics

297 of 554 branches covered (53.61%)

Branch coverage included in aggregate %.

849 of 1378 relevant lines covered (61.61%)

8.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.97
src/main/java/com/knowledgepixels/query/MetricsCollector.java
1
package com.knowledgepixels.query;
2

3
import io.micrometer.core.instrument.Gauge;
4
import io.micrometer.core.instrument.MeterRegistry;
5

6
import java.util.Map;
7
import java.util.Set;
8
import java.util.concurrent.ConcurrentHashMap;
9
import java.util.concurrent.atomic.AtomicInteger;
10

11
/**
12
 * Class to collect metrics for performance analysis.
13
 */
14
public final class MetricsCollector {
15

16
    private final AtomicInteger loadCounter = new AtomicInteger(0);
18✔
17
    private final AtomicInteger typeRepositoriesCounter = new AtomicInteger(0);
18✔
18
    private final AtomicInteger pubkeyRepositoriesCounter = new AtomicInteger(0);
18✔
19
    private final AtomicInteger fullRepositoriesCounter = new AtomicInteger(0);
18✔
20

21
    private final Map<StatusController.State, AtomicInteger> statusStates = new ConcurrentHashMap<>();
15✔
22

23
    /**
24
     * Creates new metrics collector object.
25
     *
26
     * @param meterRegistry The registry instance
27
     */
28
    public MetricsCollector(MeterRegistry meterRegistry) {
6✔
29
        // Numeric metrics
30
        Gauge.builder("registry.load.counter", loadCounter, AtomicInteger::get).register(meterRegistry);
24✔
31
        Gauge.builder("registry.type.repositories.counter", typeRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
24✔
32
        Gauge.builder("registry.pubkey.repositories.counter", pubkeyRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
24✔
33
        Gauge.builder("registry.full.repositories.counter", fullRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
24✔
34

35
        // Circuit-breaker observability: expose both the raw counter and a boolean
36
        // "breaker active" flag. The boolean is redundant with counter >= threshold
37
        // but much cleaner to visualise in Grafana (the counter can saturate well
38
        // above the threshold during a sustained outage, which makes a single
39
        // "is the breaker tripped?" alert awkward to express over the raw value).
40
        Gauge.builder("registry.loader.consecutive_batch_failures",
12✔
41
                        () -> (double) JellyNanopubLoader.consecutiveBatchFailures)
×
42
                .description("Consecutive loadUpdates batches that threw an exception before succeeding")
6✔
43
                .register(meterRegistry);
6✔
44
        Gauge.builder("registry.loader.breaker_active",
12✔
45
                        () -> JellyNanopubLoader.consecutiveBatchFailures >= JellyNanopubLoader.BREAKER_THRESHOLD ? 1.0 : 0.0)
×
46
                .description("1 if the loader circuit breaker is tripped (consecutive failures >= threshold), 0 otherwise")
6✔
47
                .register(meterRegistry);
6✔
48
        // Liveness signal that works without log access: seconds since the last
49
        // non-exceptional loadUpdates return. Counts both "loaded a batch" and
50
        // "caught up, nothing to do" as progress. An instance whose value climbs
51
        // unbounded while peers stay low is stuck on something the other
52
        // gauges don't capture.
53
        Gauge.builder("registry.loader.last_successful_batch_age_seconds",
12✔
54
                        () -> {
55
                            long t = JellyNanopubLoader.lastSuccessfulBatchAtMs;
×
56
                            if (t == 0L) return 0.0;    // not started yet
×
57
                            return (System.currentTimeMillis() - t) / 1000.0;
×
58
                        })
59
                .description("Seconds since the last non-exceptional loadUpdates return (idle or loading)")
6✔
60
                .register(meterRegistry);
6✔
61

62
        // Status label metrics
63
        for (final var status : StatusController.State.values()) {
48✔
64
            AtomicInteger stateGauge = new AtomicInteger(0);
15✔
65
            statusStates.put(status, stateGauge);
18✔
66
            Gauge.builder("registry.server.status", stateGauge, AtomicInteger::get)
15✔
67
                    .description("Server status (1 if current)")
9✔
68
                    .tag("status", status.name())
9✔
69
                    .register(meterRegistry);
6✔
70
        }
71
    }
3✔
72

73
    /**
74
     * Updates the metrics based on the current state of the system.
75
     */
76
    public void updateMetrics() {
77
        // Update numeric metrics
78
        loadCounter.set((int) StatusController.get().getState().loadCounter);
21✔
79
        // Request repository names once, to avoid multiple calls
80
        var repoNames = TripleStore.get().getRepositoryNames();
9✔
81
        if (repoNames == null) {
6!
82
            repoNames = Set.of();
×
83
        }
84
        typeRepositoriesCounter.set(
12✔
85
                (int) repoNames
86
                        .stream()
6✔
87
                        .filter(repo -> repo.startsWith("type_"))
15✔
88
                        .count()
6✔
89
        );
90
        pubkeyRepositoriesCounter.set(
12✔
91
                (int) repoNames
92
                        .stream()
6✔
93
                        .filter(repo -> repo.startsWith("pubkey_"))
15✔
94
                        .count()
6✔
95
        );
96
        fullRepositoriesCounter.set(repoNames.size());
15✔
97

98
        // Update status gauge
99
        final var currentStatus = StatusController.get().getState().state;
12✔
100
        for (final var status : StatusController.State.values()) {
48✔
101
            statusStates.get(status).set(status.equals(currentStatus) ? 1 : 0);
35✔
102
        }
103
    }
3✔
104
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc