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

knowledgepixels / nanopub-query / 24664456478

20 Apr 2026 11:39AM UTC coverage: 59.905% (-0.3%) from 60.236%
24664456478

push

github

web-flow
Merge pull request #75 from knowledgepixels/fix/timeouts-backoff-breaker

fix/perf/feat: changes 1+7+8 — HTTP timeouts, exponential backoff, circuit breaker

293 of 544 branches covered (53.86%)

Branch coverage included in aggregate %.

841 of 1349 relevant lines covered (62.34%)

6.06 hits per line

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

86.79
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);
12✔
17
    private final AtomicInteger typeRepositoriesCounter = new AtomicInteger(0);
12✔
18
    private final AtomicInteger pubkeyRepositoriesCounter = new AtomicInteger(0);
12✔
19
    private final AtomicInteger fullRepositoriesCounter = new AtomicInteger(0);
12✔
20

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

23
    /**
24
     * Creates new metrics collector object.
25
     *
26
     * @param meterRegistry The registry instance
27
     */
28
    public MetricsCollector(MeterRegistry meterRegistry) {
4✔
29
        // Numeric metrics
30
        Gauge.builder("registry.load.counter", loadCounter, AtomicInteger::get).register(meterRegistry);
16✔
31
        Gauge.builder("registry.type.repositories.counter", typeRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
16✔
32
        Gauge.builder("registry.pubkey.repositories.counter", pubkeyRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
16✔
33
        Gauge.builder("registry.full.repositories.counter", fullRepositoriesCounter, AtomicInteger::get).register(meterRegistry);
16✔
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",
8✔
41
                        () -> (double) JellyNanopubLoader.consecutiveBatchFailures)
×
42
                .description("Consecutive loadUpdates batches that threw an exception before succeeding")
4✔
43
                .register(meterRegistry);
4✔
44
        Gauge.builder("registry.loader.breaker_active",
8✔
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")
4✔
47
                .register(meterRegistry);
4✔
48

49
        // Status label metrics
50
        for (final var status : StatusController.State.values()) {
32✔
51
            AtomicInteger stateGauge = new AtomicInteger(0);
10✔
52
            statusStates.put(status, stateGauge);
12✔
53
            Gauge.builder("registry.server.status", stateGauge, AtomicInteger::get)
10✔
54
                    .description("Server status (1 if current)")
6✔
55
                    .tag("status", status.name())
6✔
56
                    .register(meterRegistry);
4✔
57
        }
58
    }
2✔
59

60
    /**
61
     * Updates the metrics based on the current state of the system.
62
     */
63
    public void updateMetrics() {
64
        // Update numeric metrics
65
        loadCounter.set((int) StatusController.get().getState().loadCounter);
14✔
66
        // Request repository names once, to avoid multiple calls
67
        var repoNames = TripleStore.get().getRepositoryNames();
6✔
68
        if (repoNames == null) {
4!
69
            repoNames = Set.of();
×
70
        }
71
        typeRepositoriesCounter.set(
8✔
72
                (int) repoNames
73
                        .stream()
4✔
74
                        .filter(repo -> repo.startsWith("type_"))
10✔
75
                        .count()
4✔
76
        );
77
        pubkeyRepositoriesCounter.set(
8✔
78
                (int) repoNames
79
                        .stream()
4✔
80
                        .filter(repo -> repo.startsWith("pubkey_"))
10✔
81
                        .count()
4✔
82
        );
83
        fullRepositoriesCounter.set(repoNames.size());
10✔
84

85
        // Update status gauge
86
        final var currentStatus = StatusController.get().getState().state;
8✔
87
        for (final var status : StatusController.State.values()) {
32✔
88
            statusStates.get(status).set(status.equals(currentStatus) ? 1 : 0);
22!
89
        }
90
    }
2✔
91
}
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