1 package com.atlassian.vcache.internal.core.metrics;
2
3 import com.atlassian.vcache.ExternalCache;
4
5 import java.util.Map;
6 import java.util.Optional;
7 import java.util.Set;
8 import java.util.concurrent.CompletionStage;
9 import java.util.function.Function;
10 import java.util.function.Supplier;
11
12 import static com.atlassian.vcache.internal.MetricLabel.NUMBER_OF_FACTORY_KEYS;
13 import static com.atlassian.vcache.internal.MetricLabel.NUMBER_OF_FAILED_GET;
14 import static com.atlassian.vcache.internal.MetricLabel.NUMBER_OF_HITS;
15 import static com.atlassian.vcache.internal.MetricLabel.NUMBER_OF_MISSES;
16 import static com.atlassian.vcache.internal.MetricLabel.TIMED_FACTORY_CALL;
17 import static com.atlassian.vcache.internal.MetricLabel.TIMED_GET_CALL;
18 import static com.atlassian.vcache.internal.MetricLabel.TIMED_SUPPLIER_CALL;
19 import static com.atlassian.vcache.internal.core.VCacheCoreUtils.whenPositive;
20 import static com.atlassian.vcache.internal.core.metrics.CacheType.EXTERNAL;
21 import static com.atlassian.vcache.internal.core.metrics.TimedUtils.whenCompletableFuture;
22 import static java.util.Objects.requireNonNull;
23
24
25
26
27
28
29
30 abstract class TimedExternalCache<V> implements ExternalCache<V> {
31 protected final MetricsRecorder metricsRecorder;
32
33 TimedExternalCache(MetricsRecorder metricsRecorder) {
34 this.metricsRecorder = requireNonNull(metricsRecorder);
35 }
36
37 protected abstract ExternalCache<V> getDelegate();
38
39 @Override
40 public CompletionStage<Optional<V>> get(String key) {
41 try (ElapsedTimer ignored = new ElapsedTimer(
42 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t))) {
43 final CompletionStage<Optional<V>> result = getDelegate().get(key);
44
45 whenCompletableFuture(result, future -> {
46 if (future.isCompletedExceptionally()) {
47 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
48 } else {
49 final Optional<V> rj = future.join();
50 metricsRecorder.record(
51 getDelegate().getName(),
52 EXTERNAL,
53 rj.isPresent() ? NUMBER_OF_HITS : NUMBER_OF_MISSES,
54 1);
55 }
56 });
57
58 return result;
59 }
60 }
61
62 @Override
63 public CompletionStage<V> get(String key, Supplier<V> supplier) {
64 try (ElapsedTimer ignored = new ElapsedTimer(
65 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t));
66 TimedSupplier<V> timedSupplier = new TimedSupplier<>(supplier, this::handleTimedSupplier)) {
67 final CompletionStage<V> result = getDelegate().get(key, timedSupplier);
68
69 whenCompletableFuture(result, future -> {
70 if (future.isCompletedExceptionally()) {
71 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
72 }
73 });
74
75 return result;
76 }
77 }
78
79 @Override
80 public CompletionStage<Map<String, Optional<V>>> getBulk(Iterable<String> keys) {
81 try (ElapsedTimer ignored = new ElapsedTimer(
82 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t))) {
83 final CompletionStage<Map<String, Optional<V>>> result = getDelegate().getBulk(keys);
84
85 whenCompletableFuture(result, future -> {
86 if (future.isCompletedExceptionally()) {
87 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
88 } else {
89 final Map<String, Optional<V>> rj = future.join();
90
91 final long hits = rj.values().stream().filter(Optional::isPresent).count();
92 whenPositive(hits,
93 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_HITS, v));
94 whenPositive(rj.size() - hits,
95 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_MISSES, v));
96 }
97 });
98
99 return result;
100 }
101 }
102
103 @Override
104 public CompletionStage<Map<String, V>> getBulk
105 (Function<Set<String>, Map<String, V>> factory, Iterable<String> keys) {
106 try (ElapsedTimer ignored = new ElapsedTimer(
107 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t));
108 TimedFactory<String, V> timedFactory = new TimedFactory<>(factory, this::handleTimedFactory)) {
109 final CompletionStage<Map<String, V>> result = getDelegate().getBulk(timedFactory, keys);
110
111 whenCompletableFuture(result, future -> {
112 if (future.isCompletedExceptionally()) {
113 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
114 } else {
115 final Map<String, V> rj = future.join();
116 whenPositive(rj.size() - timedFactory.getNumberOfKeys(),
117 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_HITS, v));
118 whenPositive(timedFactory.getNumberOfKeys(),
119 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_MISSES, v));
120 }
121 });
122
123 return result;
124 }
125 }
126
127 @Override
128 public String getName() {
129 return getDelegate().getName();
130 }
131
132 protected void handleTimedSupplier(Optional<Long> time) {
133 if (time.isPresent()) {
134 metricsRecorder.record(
135 getDelegate().getName(), EXTERNAL, TIMED_SUPPLIER_CALL, time.get());
136 }
137 metricsRecorder.record(
138 getDelegate().getName(),
139 EXTERNAL,
140 time.isPresent() ? NUMBER_OF_MISSES : NUMBER_OF_HITS,
141 1);
142 }
143
144 private void handleTimedFactory(Optional<Long> time, Long numberOfKeys) {
145 if (time.isPresent()) {
146 metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_FACTORY_CALL, time.get());
147 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FACTORY_KEYS, numberOfKeys);
148 }
149 }
150 }