Add validation for metric name convention

Require names created by developers in source code be restricted to a
subset that is not likely to confuse external metric reporting systems
that use / or . to separate metric names.

Change-Id: I5950d7340811923315d0292f6040722beadf2b6f
This commit is contained in:
Shawn Pearce 2015-11-12 13:57:28 -08:00
parent 2baf2985c4
commit ceaa6624cd
2 changed files with 66 additions and 10 deletions

View File

@ -52,6 +52,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/** /**
* Connects Gerrit metric package onto DropWizard. * Connects Gerrit metric package onto DropWizard.
@ -109,7 +110,7 @@ public class DropWizardMetricMaker extends MetricMaker {
@Override @Override
public synchronized Counter0 newCounter(String name, Description desc) { public synchronized Counter0 newCounter(String name, Description desc) {
checkCounterDescription(desc); checkCounterDescription(name, desc);
define(name, desc); define(name, desc);
return newCounterImpl(name, desc.isRate()); return newCounterImpl(name, desc.isRate());
} }
@ -118,7 +119,7 @@ public class DropWizardMetricMaker extends MetricMaker {
public synchronized <F1> Counter1<F1> newCounter( public synchronized <F1> Counter1<F1> newCounter(
String name, Description desc, String name, Description desc,
Field<F1> field1) { Field<F1> field1) {
checkCounterDescription(desc); checkCounterDescription(name, desc);
CounterImpl1<F1> m = new CounterImpl1<>(this, name, desc, field1); CounterImpl1<F1> m = new CounterImpl1<>(this, name, desc, field1);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
@ -129,7 +130,7 @@ public class DropWizardMetricMaker extends MetricMaker {
public synchronized <F1, F2> Counter2<F1, F2> newCounter( public synchronized <F1, F2> Counter2<F1, F2> newCounter(
String name, Description desc, String name, Description desc,
Field<F1> field1, Field<F2> field2) { Field<F1> field1, Field<F2> field2) {
checkCounterDescription(desc); checkCounterDescription(name, desc);
CounterImplN m = new CounterImplN(this, name, desc, field1, field2); CounterImplN m = new CounterImplN(this, name, desc, field1, field2);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
@ -140,14 +141,15 @@ public class DropWizardMetricMaker extends MetricMaker {
public synchronized <F1, F2, F3> Counter3<F1, F2, F3> newCounter( public synchronized <F1, F2, F3> Counter3<F1, F2, F3> newCounter(
String name, Description desc, String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) { Field<F1> field1, Field<F2> field2, Field<F3> field3) {
checkCounterDescription(desc); checkCounterDescription(name, desc);
CounterImplN m = new CounterImplN(this, name, desc, field1, field2, field3); CounterImplN m = new CounterImplN(this, name, desc, field1, field2, field3);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
return m.counter3(); return m.counter3();
} }
private static void checkCounterDescription(Description desc) { private static void checkCounterDescription(String name, Description desc) {
checkMetricName(name);
checkArgument(!desc.isConstant(), "counters must not be constant"); checkArgument(!desc.isConstant(), "counters must not be constant");
checkArgument(!desc.isGauge(), "counters must not be gauge"); checkArgument(!desc.isGauge(), "counters must not be gauge");
} }
@ -176,14 +178,14 @@ public class DropWizardMetricMaker extends MetricMaker {
@Override @Override
public synchronized Timer0 newTimer(String name, Description desc) { public synchronized Timer0 newTimer(String name, Description desc) {
checkTimerDescription(desc); checkTimerDescription(name, desc);
define(name, desc); define(name, desc);
return newTimerImpl(name); return newTimerImpl(name);
} }
@Override @Override
public synchronized <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) { public synchronized <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
checkTimerDescription(desc); checkTimerDescription(name, desc);
TimerImpl1<F1> m = new TimerImpl1<>(this, name, desc, field1); TimerImpl1<F1> m = new TimerImpl1<>(this, name, desc, field1);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
@ -193,7 +195,7 @@ public class DropWizardMetricMaker extends MetricMaker {
@Override @Override
public synchronized <F1, F2> Timer2<F1, F2> newTimer(String name, Description desc, public synchronized <F1, F2> Timer2<F1, F2> newTimer(String name, Description desc,
Field<F1> field1, Field<F2> field2) { Field<F1> field1, Field<F2> field2) {
checkTimerDescription(desc); checkTimerDescription(name, desc);
TimerImplN m = new TimerImplN(this, name, desc, field1, field2); TimerImplN m = new TimerImplN(this, name, desc, field1, field2);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
@ -204,14 +206,15 @@ public class DropWizardMetricMaker extends MetricMaker {
public synchronized <F1, F2, F3> Timer3<F1, F2, F3> newTimer( public synchronized <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
String name, Description desc, String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) { Field<F1> field1, Field<F2> field2, Field<F3> field3) {
checkTimerDescription(desc); checkTimerDescription(name, desc);
TimerImplN m = new TimerImplN(this, name, desc, field1, field2, field3); TimerImplN m = new TimerImplN(this, name, desc, field1, field2, field3);
define(name, desc); define(name, desc);
bucketed.put(name, m); bucketed.put(name, m);
return m.timer3(); return m.timer3();
} }
private static void checkTimerDescription(Description desc) { private static void checkTimerDescription(String name, Description desc) {
checkMetricName(name);
checkArgument(!desc.isConstant(), "timer must not be constant"); checkArgument(!desc.isConstant(), "timer must not be constant");
checkArgument(!desc.isGauge(), "timer must not be a gauge"); checkArgument(!desc.isGauge(), "timer must not be a gauge");
checkArgument(!desc.isRate(), "timer must not be a rate"); checkArgument(!desc.isRate(), "timer must not be a rate");
@ -226,6 +229,7 @@ public class DropWizardMetricMaker extends MetricMaker {
@Override @Override
public <V> CallbackMetric0<V> newCallbackMetric( public <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc) { String name, Class<V> valueClass, Description desc) {
checkMetricName(name);
define(name, desc); define(name, desc);
return new CallbackMetricImpl0<>(this, registry, name, valueClass); return new CallbackMetricImpl0<>(this, registry, name, valueClass);
} }
@ -233,6 +237,7 @@ public class DropWizardMetricMaker extends MetricMaker {
@Override @Override
public <F1, V> CallbackMetric1<F1, V> newCallbackMetric( public <F1, V> CallbackMetric1<F1, V> newCallbackMetric(
String name, Class<V> valueClass, Description desc, Field<F1> field1) { String name, Class<V> valueClass, Description desc, Field<F1> field1) {
checkMetricName(name);
CallbackMetricImpl1<F1, V> m = new CallbackMetricImpl1<>(this, registry, CallbackMetricImpl1<F1, V> m = new CallbackMetricImpl1<>(this, registry,
name, valueClass, desc, field1); name, valueClass, desc, field1);
define(name, desc); define(name, desc);
@ -274,6 +279,15 @@ public class DropWizardMetricMaker extends MetricMaker {
descriptions.put(name, desc.getAnnotations()); descriptions.put(name, desc.getAnnotations());
} }
private static final Pattern METRIC_NAME_PATTERN = Pattern
.compile("[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)*");
private static void checkMetricName(String name) {
checkArgument(
METRIC_NAME_PATTERN.matcher(name).matches(),
"metric name must match %s", METRIC_NAME_PATTERN.pattern());
}
static String name(Description.FieldOrdering ordering, static String name(Description.FieldOrdering ordering,
String codeName, String codeName,
String fieldValues) { String fieldValues) {

View File

@ -22,6 +22,7 @@ import com.google.gerrit.metrics.CallbackMetric0;
import com.google.gerrit.metrics.Counter0; import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Counter1; import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.FieldOrdering;
import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker; import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
@ -35,12 +36,17 @@ import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class ProcMetricModuleTest { public class ProcMetricModuleTest {
@Rule
public ExpectedException exception = ExpectedException.none();
@Inject @Inject
MetricMaker metrics; MetricMaker metrics;
@ -103,6 +109,30 @@ public class ProcMetricModuleTest {
assertThat(failed.getCount()).isEqualTo(5); assertThat(failed.getCount()).isEqualTo(5);
} }
@Test
public void testCounterPrefixFields() {
Counter1<String> cntr = metrics.newCounter(
"test/count",
new Description("simple test")
.setCumulative()
.setFieldOrdering(FieldOrdering.PREFIX_FIELDS_BASENAME),
Field.ofString("action"));
Counter total = get("test/count_total", Counter.class);
assertThat(total.getCount()).isEqualTo(0);
cntr.increment("passed");
Counter passed = get("test/passed/count", Counter.class);
assertThat(total.getCount()).isEqualTo(1);
assertThat(passed.getCount()).isEqualTo(1);
cntr.incrementBy("failed", 5);
Counter failed = get("test/failed/count", Counter.class);
assertThat(total.getCount()).isEqualTo(6);
assertThat(passed.getCount()).isEqualTo(1);
assertThat(failed.getCount()).isEqualTo(5);
}
@Test @Test
public void testCallbackMetric0() { public void testCallbackMetric0() {
final CallbackMetric0<Long> cntr = metrics.newCallbackMetric( final CallbackMetric0<Long> cntr = metrics.newCallbackMetric(
@ -130,6 +160,18 @@ public class ProcMetricModuleTest {
assertThat(invocations.get()).isEqualTo(1); assertThat(invocations.get()).isEqualTo(1);
} }
@Test
public void testInvalidName1() {
exception.expect(IllegalArgumentException.class);
metrics.newCounter("invalid name", new Description("fail"));
}
@Test
public void testInvalidName2() {
exception.expect(IllegalArgumentException.class);
metrics.newCounter("invalid/ name", new Description("fail"));
}
@SuppressWarnings({"unchecked", "cast"}) @SuppressWarnings({"unchecked", "cast"})
private <V> Gauge<V> gauge(String name) { private <V> Gauge<V> gauge(String name) {
return (Gauge<V>) get(name, Gauge.class); return (Gauge<V>) get(name, Gauge.class);