View Javadoc

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   * Wrapper for a {@link ExternalCache} that records metrics.
26   *
27   * @param <V> the value type
28   * @since 1.0.0
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 }