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                  }
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 }