Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dynatrace v2] Update to utils library v2 #4064

2 changes: 1 addition & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ def VERSIONS = [
'ch.qos.logback:logback-classic:1.2.+',
'colt:colt:1.2.0',
'com.amazonaws:aws-java-sdk-cloudwatch:latest.release',
'com.dynatrace.metric.util:dynatrace-metric-utils-java:1.+',
'com.dynatrace.metric.util:dynatrace-metric-utils-java:latest.release',
'com.fasterxml.jackson.core:jackson-databind:latest.release',
'com.github.ben-manes.caffeine:caffeine:2.+',
'com.github.charithe:kafka-junit:latest.release',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

Expand Down Expand Up @@ -71,22 +70,33 @@ public final class DynatraceExporterV2 extends AbstractDynatraceExporter {
// This should be non-static for MockLoggerFactory.injectLogger() in tests.
private final InternalLogger logger = InternalLoggerFactory.getInstance(DynatraceExporterV2.class);

private final MetricBuilderFactory metricBuilderFactory;
private MetricLinePreConfiguration preConfiguration;

private boolean skipExport = false;

public DynatraceExporterV2(DynatraceConfig config, Clock clock, HttpSender httpClient) {
super(config, clock, httpClient);

logger.info("Exporting to endpoint {}", config.uri());

MetricBuilderFactory.MetricBuilderFactoryBuilder factoryBuilder = MetricBuilderFactory.builder()
.withPrefix(config.metricKeyPrefix())
.withDefaultDimensions(parseDefaultDimensions(config.defaultDimensions()));
try {
MetricLinePreConfiguration.Builder preConfigBuilder = MetricLinePreConfiguration.builder()
.prefix(config.metricKeyPrefix())
.defaultDimensions(enrichWithMetricsSourceDimension(config.defaultDimensions()));

if (config.enrichWithDynatraceMetadata()) {
factoryBuilder.withDynatraceMetadata();
}
if (config.enrichWithDynatraceMetadata()) {
preConfigBuilder.dynatraceMetadataDimensions();
}

metricBuilderFactory = factoryBuilder.build();
preConfiguration = preConfigBuilder.build();
}
catch (MetricException e) {
// if the preconfiguration is invalid, all created metric lines would be
// invalid, and exporting any line becomes useless. Therefore, we log an
// error, and don't export at all.
logger.error(e.getMessage());
skipExport = true;
}
jonatan-ivanov marked this conversation as resolved.
Show resolved Hide resolved
}

private boolean isValidEndpoint(String uri) {
Expand Down Expand Up @@ -114,12 +124,10 @@ private boolean shouldIgnoreToken(DynatraceConfig config) {
return false;
}

private DimensionList parseDefaultDimensions(Map<String, String> defaultDimensions) {
List<Dimension> dimensions = Stream
.concat(defaultDimensions.entrySet().stream(), staticDimensions.entrySet().stream())
.map(entry -> Dimension.create(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
return DimensionList.fromCollection(dimensions);
private Map<String, String> enrichWithMetricsSourceDimension(Map<String, String> defaultDimensions) {
LinkedHashMap<String, String> orderedDimensions = new LinkedHashMap<>(defaultDimensions);
orderedDimensions.putAll(staticDimensions);
return orderedDimensions;
}

/**
Expand All @@ -133,6 +141,11 @@ private DimensionList parseDefaultDimensions(Map<String, String> defaultDimensio
*/
@Override
public void export(List<Meter> meters) {
if (skipExport) {
logger.warn("Dynatrace configuration is invalid, skipping export.");
return;
}

Map<String, String> seenMetadata = null;
if (config.exportMeterMetadata()) {
seenMetadata = new HashMap<>();
Expand Down Expand Up @@ -209,11 +222,11 @@ private String createGaugeLine(Meter meter, Map<String, String> seenMetadata, Me
meter.getId().getName()));
return null;
}
Metric.Builder metricBuilder = createMetricBuilder(meter).setDoubleGaugeValue(value);
MetricLineBuilder.GaugeStep gaugeStep = createTypeStep(meter).gauge();

storeMetadataLine(metricBuilder, seenMetadata);
storeMetadataLine(createMetadataStep(gaugeStep, meter), seenMetadata);

return metricBuilder.serializeMetricLine();
return gaugeStep.value(value).timestamp(Instant.ofEpochMilli(clock.wallTime())).build();
}
catch (MetricException e) {
logger.warn(METER_EXCEPTION_LOG_FORMAT, meter.getId(), e.getMessage());
Expand All @@ -229,12 +242,11 @@ Stream<String> toCounterLine(Counter counter, Map<String, String> seenMetadata)

private String createCounterLine(Meter meter, Map<String, String> seenMetadata, Measurement measurement) {
try {
Metric.Builder metricBuilder = createMetricBuilder(meter)
.setDoubleCounterValueDelta(measurement.getValue());
MetricLineBuilder.CounterStep counterStep = createTypeStep(meter).count();

storeMetadataLine(metricBuilder, seenMetadata);
storeMetadataLine(createMetadataStep(counterStep, meter), seenMetadata);

return metricBuilder.serializeMetricLine();
return counterStep.delta(measurement.getValue()).timestamp(Instant.ofEpochMilli(clock.wallTime())).build();
}
catch (MetricException e) {
logger.warn(METER_EXCEPTION_LOG_FORMAT, meter.getId(), e.getMessage());
Expand Down Expand Up @@ -285,11 +297,13 @@ private double minFromHistogramSnapshot(HistogramSnapshot histogramSnapshot, Tim
private Stream<String> createSummaryLine(Meter meter, Map<String, String> seenMetadata, double min, double max,
double total, long count) {
try {
Metric.Builder builder = createMetricBuilder(meter).setDoubleSummaryValue(min, max, total, count);
MetricLineBuilder.GaugeStep metricBuilder = createTypeStep(meter).gauge();

storeMetadataLine(builder, seenMetadata);
storeMetadataLine(createMetadataStep(metricBuilder, meter), seenMetadata);

return Stream.of(builder.serializeMetricLine());
return Stream.of(metricBuilder.summary(min, max, total, count)
.timestamp(Instant.ofEpochMilli(clock.wallTime()))
.build());
}
catch (MetricException e) {
logger.warn(METER_EXCEPTION_LOG_FORMAT, meter.getId(), e.getMessage());
Expand Down Expand Up @@ -375,17 +389,14 @@ private Stream<String> toMeterLine(Meter meter, BiFunction<Meter, Measurement, S
.filter(Objects::nonNull);
}

private Metric.Builder createMetricBuilder(Meter meter) {
return metricBuilderFactory.newMetricBuilder(meter.getId().getName())
.setDimensions(fromTags(meter.getId().getTags()))
.setTimestamp(Instant.ofEpochMilli(clock.wallTime()))
.setUnit(meter.getId().getBaseUnit())
.setDescription(meter.getId().getDescription());
}
private MetricLineBuilder.TypeStep createTypeStep(Meter meter) throws MetricException {
MetricLineBuilder.TypeStep typeStep = MetricLineBuilder.create(preConfiguration)
.metricKey(meter.getId().getName());
for (Tag tag : meter.getId().getTags()) {
typeStep.dimension(tag.getKey(), tag.getValue());
}

private DimensionList fromTags(List<Tag> tags) {
return DimensionList.fromCollection(
tags.stream().map(tag -> Dimension.create(tag.getKey(), tag.getValue())).collect(Collectors.toList()));
return typeStep;
}

private <T> Stream<T> streamOf(Iterable<T> iterable) {
Expand Down Expand Up @@ -453,37 +464,58 @@ private void handleSuccess(int totalSent, HttpSender.Response response) {
}
}

private void storeMetadataLine(Metric.Builder metricBuilder, Map<String, String> seenMetadata)
private MetricLineBuilder.MetadataStep createMetadataStep(MetricLineBuilder.GaugeStep gaugeStep, Meter meter) {
return enrichMetadataStep(gaugeStep.metadata(), meter);
}

private MetricLineBuilder.MetadataStep createMetadataStep(MetricLineBuilder.CounterStep counterStep, Meter meter) {
return enrichMetadataStep(counterStep.metadata(), meter);
}

private MetricLineBuilder.MetadataStep enrichMetadataStep(MetricLineBuilder.MetadataStep metadataStep,
Meter meter) {
return metadataStep.description(meter.getId().getDescription()).unit(meter.getId().getBaseUnit());
}

private void storeMetadataLine(MetricLineBuilder.MetadataStep metadataStep, Map<String, String> seenMetadata)
throws MetricException {
// if the config to export metadata is turned off, the seenMetadata map will be
// null.
if (seenMetadata == null) {
return;
}

String key = metricBuilder.getNormalizedMetricKey();
if (metadataStep == null) {
return;
}

String metadataLine = metadataStep.build();

if (metadataLine == null) {
return;
}

String key = extractMetricKey(metadataLine);
if (!seenMetadata.containsKey(key)) {
// if there is no metadata associated with the key, add it.
seenMetadata.put(key, metricBuilder.serializeMetadataLine());
seenMetadata.put(key, metadataLine);
}
else {
// get the previously stored metadata line
String previousMetadataLine = seenMetadata.get(key);
// if the previous line is not null, a metadata object had already been set in
// the past and no conflicting metadata lines had been added thereafter.
if (previousMetadataLine != null) {
String newMetadataLine = metricBuilder.serializeMetadataLine();
// if the new metadata line conflicts with the old one, we don't know
// which one is the correct metadata and will not export any.
// the map entry is set to null to ensure other metadata lines cannot be
// set for this metric key.
if (!previousMetadataLine.equals(newMetadataLine)) {
if (!previousMetadataLine.equals(metadataLine)) {
seenMetadata.put(key, null);
logger.warn(
"Metadata discrepancy detected:\n" + "original metadata:\t{}\n" + "tried to set new:\t{}\n"
+ "Metadata for metric key {} will not be sent.",
previousMetadataLine, newMetadataLine, key);
previousMetadataLine, metadataLine, key);
}
}
// else:
Expand All @@ -493,4 +525,24 @@ private void storeMetadataLine(Metric.Builder metricBuilder, Map<String, String>
}
}

private String extractMetricKey(String metadataLine) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't MetadataStep do this for us instead of encoding it into a string and then trying to decode it again?
This also feels quite brittle. Btw should the index start at 1?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw should the index start at 1?

Yes that's on purpose, the first index will always be #, I added a clarifying comment in 0d6d7f0

if (metadataLine == null) {
return null;
}

StringBuilder metricKey = new StringBuilder(32);

// Start at index 1 as index 0 will always be '#'
for (int i = 1; i < metadataLine.length(); i++) {
char c = metadataLine.charAt(i);
if (c == ' ' || c == ',') {
break;
}

metricKey.append(c);
}

return metricKey.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ void shouldSendProperRequest() throws Throwable {

String[] lines = new String(request.getEntity(), StandardCharsets.UTF_8).trim().split("\n");
assertThat(lines).hasSize(4)
.containsExactly("my.counter,dt.metrics.source=micrometer count,delta=12.0 " + clock.wallTime(),
"my.timer,dt.metrics.source=micrometer gauge,min=12.0,max=42.0,sum=108.0,count=4 "
+ clock.wallTime(),
"my.gauge,dt.metrics.source=micrometer gauge," + gauge + " " + clock.wallTime(),
.containsExactly("my.counter,dt.metrics.source=micrometer count,delta=12 " + clock.wallTime(),
"my.timer,dt.metrics.source=micrometer gauge,min=12,max=42,sum=108,count=4 " + clock.wallTime(),
"my.gauge,dt.metrics.source=micrometer gauge," + formatDouble(gauge) + " " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds");
})));
}
Expand All @@ -113,8 +112,8 @@ void shouldResetBetweenRequests() throws Throwable {

assertThat(request.getEntity()).asString()
.hasLineCount(2)
.contains("my.timer,dt.metrics.source=micrometer gauge,min=22.0,max=50.0,sum=72.0,count=2 "
+ clock.wallTime(), "#my.timer gauge dt.meta.unit=milliseconds");
.contains("my.timer,dt.metrics.source=micrometer gauge,min=22,max=50,sum=72,count=2 " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds");

// both are bigger than the previous min and smaller than the previous max. They
// will only show up if the
Expand All @@ -131,8 +130,9 @@ void shouldResetBetweenRequests() throws Throwable {

assertThat(request2.getEntity()).asString()
.hasLineCount(2)
.containsIgnoringNewLines("my.timer,dt.metrics.source=micrometer gauge,min=33.0,max=44.0,sum=77.0,count=2 "
+ clock.wallTime(), "#my.timer gauge dt.meta.unit=milliseconds");
.containsIgnoringNewLines(
"my.timer,dt.metrics.source=micrometer gauge,min=33,max=44,sum=77,count=2 " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds");
}

@Test
Expand All @@ -148,8 +148,9 @@ void shouldNotTrackPercentilesWithDynatraceSummary() throws Throwable {

verify(httpClient).send(assertArg((request -> assertThat(request.getEntity()).asString()
.hasLineCount(2)
.containsIgnoringNewLines("my.timer,dt.metrics.source=micrometer gauge,min=22.0,max=55.0,sum=77.0,count=2 "
+ clock.wallTime(), "#my.timer gauge dt.meta.unit=milliseconds"))));
.containsIgnoringNewLines(
"my.timer,dt.metrics.source=micrometer gauge,min=22,max=55,sum=77,count=2 " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds"))));
}

@Test
Expand All @@ -165,8 +166,9 @@ void shouldNotExportLinesWithZeroCount() throws Throwable {

verify(httpClient).send(assertArg(request -> assertThat(request.getEntity()).asString()
.hasLineCount(2)
.containsIgnoringNewLines("my.timer,dt.metrics.source=micrometer gauge,min=44.0,max=44.0,sum=44.0,count=1 "
+ clock.wallTime(), "#my.timer gauge dt.meta.unit=milliseconds")));
.containsIgnoringNewLines(
"my.timer,dt.metrics.source=micrometer gauge,min=44,max=44,sum=44,count=1 " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds")));

// reset for next export interval
reset(httpClient);
Expand All @@ -190,8 +192,9 @@ void shouldNotExportLinesWithZeroCount() throws Throwable {

verify(httpClient).send(assertArg(request -> assertThat(request.getEntity()).asString()
.hasLineCount(2)
.containsIgnoringNewLines("my.timer,dt.metrics.source=micrometer gauge,min=33.0,max=33.0,sum=33.0,count=1 "
+ clock.wallTime(), "#my.timer gauge dt.meta.unit=milliseconds")));
.containsIgnoringNewLines(
"my.timer,dt.metrics.source=micrometer gauge,min=33,max=33,sum=33,count=1 " + clock.wallTime(),
"#my.timer gauge dt.meta.unit=milliseconds")));
}

private DynatraceConfig createDefaultDynatraceConfig() {
Expand All @@ -218,4 +221,12 @@ public DynatraceApiVersion apiVersion() {
};
}

private String formatDouble(double value) {
if (value == (long) value) {
return Long.toString((long) value);
}

return Double.toString(value);
}

}