Line data Source code
1 : // Copyright (C) 2015 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.metrics.dropwizard;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.gerrit.metrics.dropwizard.MetricResource.METRIC_KIND;
19 : import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
20 :
21 : import com.codahale.metrics.Histogram;
22 : import com.codahale.metrics.Metric;
23 : import com.codahale.metrics.MetricRegistry;
24 : import com.codahale.metrics.Timer;
25 : import com.google.common.collect.FluentIterable;
26 : import com.google.common.collect.ImmutableMap;
27 : import com.google.common.collect.ImmutableSet;
28 : import com.google.gerrit.extensions.registration.DynamicMap;
29 : import com.google.gerrit.extensions.registration.RegistrationHandle;
30 : import com.google.gerrit.extensions.restapi.RestApiModule;
31 : import com.google.gerrit.metrics.CallbackMetric;
32 : import com.google.gerrit.metrics.CallbackMetric0;
33 : import com.google.gerrit.metrics.CallbackMetric1;
34 : import com.google.gerrit.metrics.Counter0;
35 : import com.google.gerrit.metrics.Counter1;
36 : import com.google.gerrit.metrics.Counter2;
37 : import com.google.gerrit.metrics.Counter3;
38 : import com.google.gerrit.metrics.Description;
39 : import com.google.gerrit.metrics.Description.FieldOrdering;
40 : import com.google.gerrit.metrics.Field;
41 : import com.google.gerrit.metrics.Histogram0;
42 : import com.google.gerrit.metrics.Histogram1;
43 : import com.google.gerrit.metrics.Histogram2;
44 : import com.google.gerrit.metrics.Histogram3;
45 : import com.google.gerrit.metrics.MetricMaker;
46 : import com.google.gerrit.metrics.MetricsReservoirConfig;
47 : import com.google.gerrit.metrics.Timer0;
48 : import com.google.gerrit.metrics.Timer1;
49 : import com.google.gerrit.metrics.Timer2;
50 : import com.google.gerrit.metrics.Timer3;
51 : import com.google.gerrit.metrics.proc.JGitMetricModule;
52 : import com.google.gerrit.metrics.proc.ProcMetricModule;
53 : import com.google.gerrit.server.cache.CacheMetrics;
54 : import com.google.gerrit.server.config.MetricsReservoirConfigImpl;
55 : import com.google.inject.Inject;
56 : import com.google.inject.Scopes;
57 : import com.google.inject.Singleton;
58 : import java.util.Map;
59 : import java.util.Optional;
60 : import java.util.Set;
61 : import java.util.concurrent.ConcurrentHashMap;
62 : import java.util.concurrent.TimeUnit;
63 : import java.util.regex.Pattern;
64 :
65 : /**
66 : * Connects Gerrit metric package onto DropWizard.
67 : *
68 : * @see <a href="http://www.dropwizard.io/">DropWizard</a>
69 : */
70 : @Singleton
71 : public class DropWizardMetricMaker extends MetricMaker {
72 : public static class ApiModule extends RestApiModule {
73 : private final Optional<MetricsReservoirConfig> metricsReservoirConfig;
74 :
75 1 : public ApiModule(MetricsReservoirConfig metricsReservoirConfig) {
76 1 : this.metricsReservoirConfig = Optional.of(metricsReservoirConfig);
77 1 : }
78 :
79 15 : public ApiModule() {
80 15 : this.metricsReservoirConfig = Optional.empty();
81 15 : }
82 :
83 : @Override
84 : protected void configure() {
85 16 : if (metricsReservoirConfig.isPresent()) {
86 1 : bind(MetricsReservoirConfig.class).toInstance(metricsReservoirConfig.get());
87 : } else {
88 15 : bind(MetricsReservoirConfig.class)
89 15 : .to(MetricsReservoirConfigImpl.class)
90 15 : .in(Scopes.SINGLETON);
91 : }
92 16 : bind(MetricRegistry.class).in(Scopes.SINGLETON);
93 16 : bind(DropWizardMetricMaker.class).in(Scopes.SINGLETON);
94 16 : bind(MetricMaker.class).to(DropWizardMetricMaker.class);
95 :
96 16 : install(new ProcMetricModule());
97 16 : install(new JGitMetricModule());
98 16 : }
99 : }
100 :
101 138 : public static class RestModule extends RestApiModule {
102 : @Override
103 : protected void configure() {
104 138 : DynamicMap.mapOf(binder(), METRIC_KIND);
105 138 : child(CONFIG_KIND, "metrics").to(MetricsCollection.class);
106 138 : get(METRIC_KIND).to(GetMetric.class);
107 138 : bind(CacheMetrics.class);
108 138 : }
109 : }
110 :
111 : private final MetricRegistry registry;
112 : private final Map<String, BucketedMetric> bucketed;
113 : private final Map<String, ImmutableMap<String, String>> descriptions;
114 : private final MetricsReservoirConfig reservoirConfig;
115 :
116 : @Inject
117 140 : DropWizardMetricMaker(MetricRegistry registry, MetricsReservoirConfig reservoirConfig) {
118 140 : this.registry = registry;
119 140 : this.bucketed = new ConcurrentHashMap<>();
120 140 : this.descriptions = new ConcurrentHashMap<>();
121 140 : this.reservoirConfig = reservoirConfig;
122 140 : }
123 :
124 : Iterable<String> getMetricNames() {
125 0 : return descriptions.keySet();
126 : }
127 :
128 : /** Get the underlying metric implementation. */
129 : public Metric getMetric(String name) {
130 0 : Metric m = bucketed.get(name);
131 0 : return m != null ? m : registry.getMetrics().get(name);
132 : }
133 :
134 : /** Lookup annotations from a metric's {@link Description}. */
135 : public ImmutableMap<String, String> getAnnotations(String name) {
136 0 : return descriptions.get(name);
137 : }
138 :
139 : @Override
140 : public synchronized Counter0 newCounter(String name, Description desc) {
141 16 : checkCounterDescription(name, desc);
142 16 : define(name, desc);
143 16 : return newCounterImpl(name, desc.isRate());
144 : }
145 :
146 : @Override
147 : public synchronized <F1> Counter1<F1> newCounter(
148 : String name, Description desc, Field<F1> field1) {
149 16 : checkCounterDescription(name, desc);
150 16 : CounterImpl1<F1> m = new CounterImpl1<>(this, name, desc, field1);
151 16 : define(name, desc);
152 16 : bucketed.put(name, m);
153 16 : return m.counter();
154 : }
155 :
156 : @Override
157 : public synchronized <F1, F2> Counter2<F1, F2> newCounter(
158 : String name, Description desc, Field<F1> field1, Field<F2> field2) {
159 15 : checkCounterDescription(name, desc);
160 15 : CounterImplN m = new CounterImplN(this, name, desc, field1, field2);
161 15 : define(name, desc);
162 15 : bucketed.put(name, m);
163 15 : return m.counter2();
164 : }
165 :
166 : @Override
167 : public synchronized <F1, F2, F3> Counter3<F1, F2, F3> newCounter(
168 : String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
169 15 : checkCounterDescription(name, desc);
170 15 : CounterImplN m = new CounterImplN(this, name, desc, field1, field2, field3);
171 15 : define(name, desc);
172 15 : bucketed.put(name, m);
173 15 : return m.counter3();
174 : }
175 :
176 : private static void checkCounterDescription(String name, Description desc) {
177 16 : checkMetricName(name);
178 16 : checkArgument(!desc.isConstant(), "counter must not be constant");
179 16 : checkArgument(!desc.isGauge(), "counter must not be gauge");
180 16 : }
181 :
182 : CounterImpl newCounterImpl(String name, boolean isRate) {
183 16 : if (isRate) {
184 15 : final com.codahale.metrics.Meter m = registry.meter(name);
185 15 : return new CounterImpl(name, m) {
186 : @Override
187 : public void incrementBy(long delta) {
188 15 : checkArgument(delta >= 0, "counter delta must be >= 0");
189 15 : m.mark(delta);
190 15 : }
191 : };
192 : }
193 16 : final com.codahale.metrics.Counter m = registry.counter(name);
194 16 : return new CounterImpl(name, m) {
195 : @Override
196 : public void incrementBy(long delta) {
197 16 : checkArgument(delta >= 0, "counter delta must be >= 0");
198 16 : m.inc(delta);
199 16 : }
200 : };
201 : }
202 :
203 : @Override
204 : public synchronized Timer0 newTimer(String name, Description desc) {
205 16 : checkTimerDescription(name, desc);
206 16 : define(name, desc);
207 16 : return newTimerImpl(name);
208 : }
209 :
210 : @Override
211 : public synchronized <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
212 15 : checkTimerDescription(name, desc);
213 15 : TimerImpl1<F1> m = new TimerImpl1<>(this, name, desc, field1);
214 15 : define(name, desc);
215 15 : bucketed.put(name, m);
216 15 : return m.timer();
217 : }
218 :
219 : @Override
220 : public synchronized <F1, F2> Timer2<F1, F2> newTimer(
221 : String name, Description desc, Field<F1> field1, Field<F2> field2) {
222 15 : checkTimerDescription(name, desc);
223 15 : TimerImplN m = new TimerImplN(this, name, desc, field1, field2);
224 15 : define(name, desc);
225 15 : bucketed.put(name, m);
226 15 : return m.timer2();
227 : }
228 :
229 : @Override
230 : public synchronized <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
231 : String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
232 15 : checkTimerDescription(name, desc);
233 15 : TimerImplN m = new TimerImplN(this, name, desc, field1, field2, field3);
234 15 : define(name, desc);
235 15 : bucketed.put(name, m);
236 15 : return m.timer3();
237 : }
238 :
239 : private static void checkTimerDescription(String name, Description desc) {
240 16 : checkMetricName(name);
241 16 : checkArgument(!desc.isConstant(), "timer must not be constant");
242 16 : checkArgument(!desc.isGauge(), "timer must not be a gauge");
243 16 : checkArgument(!desc.isRate(), "timer must not be a rate");
244 16 : checkArgument(desc.isCumulative(), "timer must be cumulative");
245 16 : checkArgument(desc.getTimeUnit() != null, "timer must have a unit");
246 16 : }
247 :
248 : TimerImpl newTimerImpl(String name) {
249 16 : return new TimerImpl(
250 : name,
251 16 : registry.timer(name, () -> new Timer(DropWizardReservoirProvider.get(reservoirConfig))));
252 : }
253 :
254 : @Override
255 : public synchronized Histogram0 newHistogram(String name, Description desc) {
256 0 : checkHistogramDescription(name, desc);
257 0 : define(name, desc);
258 0 : return newHistogramImpl(name);
259 : }
260 :
261 : @Override
262 : public synchronized <F1> Histogram1<F1> newHistogram(
263 : String name, Description desc, Field<F1> field1) {
264 15 : checkHistogramDescription(name, desc);
265 15 : HistogramImpl1<F1> m = new HistogramImpl1<>(this, name, desc, field1);
266 15 : define(name, desc);
267 15 : bucketed.put(name, m);
268 15 : return m.histogram1();
269 : }
270 :
271 : @Override
272 : public synchronized <F1, F2> Histogram2<F1, F2> newHistogram(
273 : String name, Description desc, Field<F1> field1, Field<F2> field2) {
274 0 : checkHistogramDescription(name, desc);
275 0 : HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2);
276 0 : define(name, desc);
277 0 : bucketed.put(name, m);
278 0 : return m.histogram2();
279 : }
280 :
281 : @Override
282 : public synchronized <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(
283 : String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
284 0 : checkHistogramDescription(name, desc);
285 0 : HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2, field3);
286 0 : define(name, desc);
287 0 : bucketed.put(name, m);
288 0 : return m.histogram3();
289 : }
290 :
291 : private static void checkHistogramDescription(String name, Description desc) {
292 15 : checkMetricName(name);
293 15 : checkArgument(!desc.isConstant(), "histogram must not be constant");
294 15 : checkArgument(!desc.isGauge(), "histogram must not be a gauge");
295 15 : checkArgument(!desc.isRate(), "histogram must not be a rate");
296 15 : checkArgument(desc.isCumulative(), "histogram must be cumulative");
297 15 : }
298 :
299 : HistogramImpl newHistogramImpl(String name) {
300 15 : return new HistogramImpl(
301 : name,
302 15 : registry.histogram(
303 15 : name, () -> new Histogram(DropWizardReservoirProvider.get(reservoirConfig))));
304 : }
305 :
306 : @Override
307 : public <V> CallbackMetric0<V> newCallbackMetric(
308 : String name, Class<V> valueClass, Description desc) {
309 16 : checkMetricName(name);
310 16 : define(name, desc);
311 16 : return new CallbackMetricImpl0<>(this, registry, name, valueClass);
312 : }
313 :
314 : @Override
315 : public <F1, V> CallbackMetric1<F1, V> newCallbackMetric(
316 : String name, Class<V> valueClass, Description desc, Field<F1> field1) {
317 16 : checkMetricName(name);
318 16 : CallbackMetricImpl1<F1, V> m =
319 : new CallbackMetricImpl1<>(this, registry, name, valueClass, desc, field1);
320 16 : define(name, desc);
321 16 : bucketed.put(name, m);
322 16 : return m.create();
323 : }
324 :
325 : @Override
326 : public synchronized RegistrationHandle newTrigger(
327 : Set<CallbackMetric<?>> metrics, Runnable trigger) {
328 16 : ImmutableSet<CallbackMetricGlue> all =
329 16 : FluentIterable.from(metrics).transform(m -> (CallbackMetricGlue) m).toSet();
330 :
331 16 : trigger = new CallbackGroup(trigger, all);
332 16 : for (CallbackMetricGlue m : all) {
333 16 : m.register(trigger);
334 16 : }
335 16 : trigger.run();
336 :
337 16 : return () -> all.forEach(CallbackMetricGlue::remove);
338 : }
339 :
340 : synchronized void remove(String name) {
341 0 : bucketed.remove(name);
342 0 : descriptions.remove(name);
343 0 : }
344 :
345 : private synchronized void define(String name, Description desc) {
346 17 : if (descriptions.containsKey(name)) {
347 15 : ImmutableMap<String, String> annotations = descriptions.get(name);
348 15 : if (!desc.getAnnotations()
349 15 : .get(Description.DESCRIPTION)
350 15 : .equals(annotations.get(Description.DESCRIPTION))) {
351 0 : throw new IllegalStateException(String.format("metric '%s' already defined", name));
352 : }
353 15 : } else {
354 17 : descriptions.put(name, desc.getAnnotations());
355 : }
356 17 : }
357 :
358 140 : private static final Pattern METRIC_NAME_PATTERN =
359 140 : Pattern.compile("[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)*");
360 :
361 : private static void checkMetricName(String name) {
362 17 : checkArgument(
363 17 : METRIC_NAME_PATTERN.matcher(name).matches(),
364 : "invalid metric name '%s': must match pattern '%s'",
365 : name,
366 17 : METRIC_NAME_PATTERN.pattern());
367 17 : }
368 :
369 : @Override
370 : public String sanitizeMetricName(String name) {
371 16 : if (METRIC_NAME_PATTERN.matcher(name).matches()) {
372 15 : return name;
373 : }
374 :
375 1 : String first = name.substring(0, 1).replaceFirst("[^\\w-]", "_");
376 1 : if (name.length() == 1) {
377 0 : return first;
378 : }
379 :
380 1 : String result = first + name.substring(1).replaceAll("/[/]+", "/").replaceAll("[^\\w-/]", "_");
381 :
382 1 : if (result.endsWith("/")) {
383 1 : result = result.substring(0, result.length() - 1);
384 : }
385 :
386 1 : return result;
387 : }
388 :
389 : static String name(Description.FieldOrdering ordering, String codeName, String fieldValues) {
390 16 : if (ordering == FieldOrdering.PREFIX_FIELDS_BASENAME) {
391 1 : int s = codeName.lastIndexOf('/');
392 1 : if (s > 0) {
393 1 : String prefix = codeName.substring(0, s);
394 1 : String metric = codeName.substring(s + 1);
395 1 : return prefix + '/' + fieldValues + '/' + metric;
396 : }
397 : }
398 16 : return codeName + '/' + fieldValues;
399 : }
400 :
401 : abstract class CounterImpl extends Counter0 {
402 : private final String name;
403 : final Metric metric;
404 :
405 16 : CounterImpl(String name, Metric metric) {
406 16 : this.name = name;
407 16 : this.metric = metric;
408 16 : }
409 :
410 : @Override
411 : public void remove() {
412 0 : descriptions.remove(name);
413 0 : registry.remove(name);
414 0 : }
415 : }
416 :
417 : class TimerImpl extends Timer0 {
418 : final com.codahale.metrics.Timer metric;
419 :
420 16 : private TimerImpl(String name, com.codahale.metrics.Timer metric) {
421 16 : super(name);
422 16 : this.metric = metric;
423 16 : }
424 :
425 : @Override
426 : protected void doRecord(long value, TimeUnit unit) {
427 15 : checkArgument(value >= 0, "timer delta must be >= 0");
428 15 : metric.update(value, unit);
429 15 : }
430 :
431 : @Override
432 : public void remove() {
433 0 : descriptions.remove(name);
434 0 : registry.remove(name);
435 0 : }
436 : }
437 :
438 : class HistogramImpl extends Histogram0 {
439 : private final String name;
440 : final com.codahale.metrics.Histogram metric;
441 :
442 15 : private HistogramImpl(String name, com.codahale.metrics.Histogram metric) {
443 15 : this.name = name;
444 15 : this.metric = metric;
445 15 : }
446 :
447 : @Override
448 : public void record(long value) {
449 7 : metric.update(value);
450 7 : }
451 :
452 : @Override
453 : public void remove() {
454 0 : descriptions.remove(name);
455 0 : registry.remove(name);
456 0 : }
457 : }
458 : }
|