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 } else {
73 metricsRecorder.record(
74 getDelegate().getName(),
75 EXTERNAL,
76 timedSupplier.wasInvoked() ? NUMBER_OF_MISSES : NUMBER_OF_HITS,
77 1);
78 }
79 });
80
81 return result;
82 }
83 }
84
85 @Override
86 public CompletionStage<Map<String, Optional<V>>> getBulk(Iterable<String> keys) {
87 try (ElapsedTimer ignored = new ElapsedTimer(
88 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t))) {
89 final CompletionStage<Map<String, Optional<V>>> result = getDelegate().getBulk(keys);
90
91 whenCompletableFuture(result, future -> {
92 if (future.isCompletedExceptionally()) {
93 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
94 } else {
95 final Map<String, Optional<V>> rj = future.join();
96
97 final long hits = rj.values().stream().filter(Optional::isPresent).count();
98 whenPositive(hits,
99 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_HITS, v));
100 whenPositive(rj.size() - hits,
101 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_MISSES, v));
102 }
103 });
104
105 return result;
106 }
107 }
108
109 @Override
110 public CompletionStage<Map<String, V>> getBulk
111 (Function<Set<String>, Map<String, V>> factory, Iterable<String> keys) {
112 try (ElapsedTimer ignored = new ElapsedTimer(
113 t -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_GET_CALL, t));
114 TimedFactory<String, V> timedFactory = new TimedFactory<>(factory, this::handleTimedFactory)) {
115 final CompletionStage<Map<String, V>> result = getDelegate().getBulk(timedFactory, keys);
116
117 whenCompletableFuture(result, future -> {
118 if (future.isCompletedExceptionally()) {
119 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FAILED_GET, 1);
120 } else {
121 final Map<String, V> rj = future.join();
122 whenPositive(rj.size() - timedFactory.getNumberOfKeys(),
123 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_HITS, v));
124 whenPositive(timedFactory.getNumberOfKeys(),
125 v -> metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_MISSES, v));
126 }
127 });
128
129 return result;
130 }
131 }
132
133 @Override
134 public String getName() {
135 return getDelegate().getName();
136 }
137
138 void handleTimedSupplier(Optional<Long> time) {
139 if (time.isPresent()) {
140 metricsRecorder.record(
141 getDelegate().getName(), EXTERNAL, TIMED_SUPPLIER_CALL, time.get());
142 }
143 }
144
145 private void handleTimedFactory(Optional<Long> time, Long numberOfKeys) {
146 if (time.isPresent()) {
147 metricsRecorder.record(getDelegate().getName(), EXTERNAL, TIMED_FACTORY_CALL, time.get());
148 metricsRecorder.record(getDelegate().getName(), EXTERNAL, NUMBER_OF_FACTORY_KEYS, numberOfKeys);
149 }
150 }
151 }