[statsd-emitter] Add config to send Druid process/service as tag (#8238)

* [statsd-emitter] Add serviceAsTag option

* [statsd-emitter] Refactor serviceAsTag option

* [statsd-emitter] Update statsd.md

* [statsd-emitter] add default prefix

* [statsd-emitter] update statsd.md

* [statsd-emitter] Remove extra spaces

* [statsd-emitter] Improve docs for config `dogstatsdServiceAsTag`

* [statsd-emitter] Simplify equals() for StatsDEmitterConfig.java

* [statsd-emitter] Add @Nullable for StatsDEmitterConfig.java
This commit is contained in:
Alexandre Yang 2019-08-12 22:18:44 +02:00 committed by Clint Wylie
parent 1054d85171
commit 6b4d028b96
4 changed files with 70 additions and 26 deletions

View File

@ -47,6 +47,7 @@ All the configuration parameters for the StatsD emitter are under `druid.emitter
|`druid.emitter.statsd.blankHolder`|The blank character replacement as statsD does not support path with blank character|no|"-"| |`druid.emitter.statsd.blankHolder`|The blank character replacement as statsD does not support path with blank character|no|"-"|
|`druid.emitter.statsd.dogstatsd`|Flag to enable [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) support. Causes dimensions to be included as tags, not as a part of the metric name. `convertRange` fields will be ignored.|no|false| |`druid.emitter.statsd.dogstatsd`|Flag to enable [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/) support. Causes dimensions to be included as tags, not as a part of the metric name. `convertRange` fields will be ignored.|no|false|
|`druid.emitter.statsd.dogstatsdConstantTags`|If `druid.emitter.statsd.dogstatsd` is true, the tags in the JSON list of strings will be sent with every event.|no|[]| |`druid.emitter.statsd.dogstatsdConstantTags`|If `druid.emitter.statsd.dogstatsd` is true, the tags in the JSON list of strings will be sent with every event.|no|[]|
|`druid.emitter.statsd.dogstatsdServiceAsTag`|If `druid.emitter.statsd.dogstatsd` and `druid.emitter.statsd.dogstatsdServiceAsTag` are true, druid service (e.g. `druid/broker`, `druid/coordinator`, etc) is reported as a tag (e.g. `service:druid/broker`) instead of being included in metric name (e.g. `druid.broker.my_metric`) and `druid` is used as metric prefix (e.g. `druid.query.time`).|no|false|
### Druid to StatsD Event Converter ### Druid to StatsD Event Converter

View File

@ -43,6 +43,7 @@ public class StatsDEmitter implements Emitter
private static final Logger log = new Logger(StatsDEmitter.class); private static final Logger log = new Logger(StatsDEmitter.class);
private static final char DRUID_METRIC_SEPARATOR = '/'; private static final char DRUID_METRIC_SEPARATOR = '/';
private static final String DRUID_DEFAULT_PREFIX = "druid";
private static final Pattern STATSD_SEPARATOR = Pattern.compile("[:|]"); private static final Pattern STATSD_SEPARATOR = Pattern.compile("[:|]");
private static final Pattern BLANK = Pattern.compile("\\s+"); private static final Pattern BLANK = Pattern.compile("\\s+");
private static final String[] EMPTY_ARRAY = new String[0]; private static final String[] EMPTY_ARRAY = new String[0];
@ -99,10 +100,16 @@ public class StatsDEmitter implements Emitter
Number value = metricEvent.getValue(); Number value = metricEvent.getValue();
ImmutableList.Builder<String> nameBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<String> nameBuilder = new ImmutableList.Builder<>();
nameBuilder.add(service); ImmutableMap.Builder<String, String> dimsBuilder = new ImmutableMap.Builder<>();
if (config.isDogstatsd() && config.isDogstatsdServiceAsTag()) {
dimsBuilder.put("service", service);
nameBuilder.add(DRUID_DEFAULT_PREFIX);
} else {
nameBuilder.add(service);
}
nameBuilder.add(metric); nameBuilder.add(metric);
ImmutableMap.Builder<String, String> dimsBuilder = new ImmutableMap.Builder<>();
StatsDMetric statsDMetric = converter.addFilteredUserDims(service, metric, userDims, dimsBuilder); StatsDMetric statsDMetric = converter.addFilteredUserDims(service, metric, userDims, dimsBuilder);
if (statsDMetric != null) { if (statsDMetric != null) {

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -43,6 +44,7 @@ public class StatsDEmitterConfig
@JsonProperty @JsonProperty
private final Boolean includeHost; private final Boolean includeHost;
@JsonProperty @JsonProperty
@Nullable
private final String dimensionMapPath; private final String dimensionMapPath;
@JsonProperty @JsonProperty
private final String blankHolder; private final String blankHolder;
@ -50,18 +52,21 @@ public class StatsDEmitterConfig
private final Boolean dogstatsd; private final Boolean dogstatsd;
@JsonProperty @JsonProperty
private final List<String> dogstatsdConstantTags; private final List<String> dogstatsdConstantTags;
@JsonProperty
private final Boolean dogstatsdServiceAsTag;
@JsonCreator @JsonCreator
public StatsDEmitterConfig( public StatsDEmitterConfig(
@JsonProperty("hostname") String hostname, @JsonProperty("hostname") String hostname,
@JsonProperty("port") Integer port, @JsonProperty("port") Integer port,
@JsonProperty("prefix") String prefix, @JsonProperty("prefix") @Nullable String prefix,
@JsonProperty("separator") String separator, @JsonProperty("separator") @Nullable String separator,
@JsonProperty("includeHost") Boolean includeHost, @JsonProperty("includeHost") @Nullable Boolean includeHost,
@JsonProperty("dimensionMapPath") String dimensionMapPath, @JsonProperty("dimensionMapPath") @Nullable String dimensionMapPath,
@JsonProperty("blankHolder") String blankHolder, @JsonProperty("blankHolder") @Nullable String blankHolder,
@JsonProperty("dogstatsd") Boolean dogstatsd, @JsonProperty("dogstatsd") @Nullable Boolean dogstatsd,
@JsonProperty("dogstatsdConstantTags") List<String> dogstatsdConstantTags @JsonProperty("dogstatsdConstantTags") @Nullable List<String> dogstatsdConstantTags,
@JsonProperty("dogstatsdServiceAsTag") @Nullable Boolean dogstatsdServiceAsTag
) )
{ {
this.hostname = Preconditions.checkNotNull(hostname, "StatsD hostname cannot be null."); this.hostname = Preconditions.checkNotNull(hostname, "StatsD hostname cannot be null.");
@ -73,6 +78,7 @@ public class StatsDEmitterConfig
this.blankHolder = blankHolder != null ? blankHolder : "-"; this.blankHolder = blankHolder != null ? blankHolder : "-";
this.dogstatsd = dogstatsd != null ? dogstatsd : false; this.dogstatsd = dogstatsd != null ? dogstatsd : false;
this.dogstatsdConstantTags = dogstatsdConstantTags != null ? dogstatsdConstantTags : Collections.emptyList(); this.dogstatsdConstantTags = dogstatsdConstantTags != null ? dogstatsdConstantTags : Collections.emptyList();
this.dogstatsdServiceAsTag = dogstatsdServiceAsTag != null ? dogstatsdServiceAsTag : false;
} }
@Override @Override
@ -87,37 +93,38 @@ public class StatsDEmitterConfig
StatsDEmitterConfig that = (StatsDEmitterConfig) o; StatsDEmitterConfig that = (StatsDEmitterConfig) o;
if (hostname != null ? !hostname.equals(that.hostname) : that.hostname != null) { if (!Objects.equals(hostname, that.hostname)) {
return false; return false;
} }
if (port != null ? !port.equals(that.port) : that.port != null) { if (!Objects.equals(port, that.port)) {
return false; return false;
} }
if (prefix != null ? !prefix.equals(that.prefix) : that.prefix != null) { if (!Objects.equals(prefix, that.prefix)) {
return false; return false;
} }
if (separator != null ? !separator.equals(that.separator) : that.separator != null) { if (!Objects.equals(separator, that.separator)) {
return false; return false;
} }
if (includeHost != null ? !includeHost.equals(that.includeHost) : that.includeHost != null) { if (!Objects.equals(includeHost, that.includeHost)) {
return false; return false;
} }
if (dimensionMapPath != null ? !dimensionMapPath.equals(that.dimensionMapPath) : that.dimensionMapPath != null) { if (!Objects.equals(dimensionMapPath, that.dimensionMapPath)) {
return false; return false;
} }
if (dogstatsd != null ? !dogstatsd.equals(that.dogstatsd) : that.dogstatsd != null) { if (!Objects.equals(dogstatsd, that.dogstatsd)) {
return false; return false;
} }
return dogstatsdConstantTags != null ? dogstatsdConstantTags.equals(that.dogstatsdConstantTags) if (!Objects.equals(dogstatsdServiceAsTag, that.dogstatsdServiceAsTag)) {
: that.dogstatsdConstantTags == null; return false;
}
return Objects.equals(dogstatsdConstantTags, that.dogstatsdConstantTags);
} }
@Override @Override
public int hashCode() public int hashCode()
{ {
return Objects.hash(hostname, port, prefix, separator, includeHost, dimensionMapPath, return Objects.hash(hostname, port, prefix, separator, includeHost, dimensionMapPath,
blankHolder, dogstatsd, dogstatsdConstantTags); blankHolder, dogstatsd, dogstatsdConstantTags, dogstatsdServiceAsTag);
} }
@JsonProperty @JsonProperty
@ -151,6 +158,7 @@ public class StatsDEmitterConfig
} }
@JsonProperty @JsonProperty
@Nullable
public String getDimensionMapPath() public String getDimensionMapPath()
{ {
return dimensionMapPath; return dimensionMapPath;
@ -173,4 +181,10 @@ public class StatsDEmitterConfig
{ {
return dogstatsdConstantTags; return dogstatsdConstantTags;
} }
@JsonProperty
public Boolean isDogstatsdServiceAsTag()
{
return dogstatsdServiceAsTag;
}
} }

View File

@ -33,7 +33,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, null, null), new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, null, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -52,7 +52,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, true, null), new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, true, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -71,7 +71,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, null, null), new StatsDEmitterConfig("localhost", 8888, null, null, null, null, null, null, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -99,7 +99,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, "#", true, null, null, null, null), new StatsDEmitterConfig("localhost", 8888, null, "#", true, null, null, null, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -127,7 +127,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, "#", true, null, null, true, null), new StatsDEmitterConfig("localhost", 8888, null, "#", true, null, null, true, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -157,7 +157,7 @@ public class StatsDEmitterTest
{ {
StatsDClient client = EasyMock.createMock(StatsDClient.class); StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter( StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, null, true, null, null, null, null), new StatsDEmitterConfig("localhost", 8888, null, null, true, null, null, null, null, null),
new ObjectMapper(), new ObjectMapper(),
client client
); );
@ -170,4 +170,26 @@ public class StatsDEmitterTest
); );
EasyMock.verify(client); EasyMock.verify(client);
} }
@Test
public void testServiceAsTagOption()
{
StatsDClient client = EasyMock.createMock(StatsDClient.class);
StatsDEmitter emitter = new StatsDEmitter(
new StatsDEmitterConfig("localhost", 8888, null, null, true, null, null, true, null, true),
new ObjectMapper(),
client
);
client.time("druid.query.time", 10,
"service:druid/broker", "dataSource:data-source", "type:groupBy", "hostname:brokerHost1"
);
EasyMock.replay(client);
emitter.emit(new ServiceMetricEvent.Builder()
.setDimension("dataSource", "data-source")
.setDimension("type", "groupBy")
.build(DateTimes.nowUtc(), "query/time", 10)
.build("druid/broker", "brokerHost1")
);
EasyMock.verify(client);
}
} }