Skip to content

Commit

Permalink
Merge pull request #4064 from mkleinegger, pirgeo, arminru, and pichl…
Browse files Browse the repository at this point in the history
…ermarc

* update-util-library:
  Polish "Upgrade dynatrace-metric-utils to 2.x"
  Upgrade dynatrace-metric-utils to 2.x

Closes gh-4064
  • Loading branch information
jonatan-ivanov committed Sep 20, 2023
2 parents a9edff2 + 3630288 commit 8d18d8c
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 106 deletions.
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 @@ -16,6 +16,8 @@
package io.micrometer.dynatrace.v2;

import com.dynatrace.metric.util.*;
import com.dynatrace.metric.util.MetricLineBuilder.MetadataStep;
import io.micrometer.common.lang.NonNull;
import io.micrometer.common.util.StringUtils;
import io.micrometer.common.util.internal.logging.InternalLogger;
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
Expand All @@ -38,7 +40,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 +72,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 pre-configuration 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("Dynatrace configuration is invalid", e);
skipExport = true;
}
}

private boolean isValidEndpoint(String uri) {
Expand Down Expand Up @@ -114,12 +126,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 @@ -132,7 +142,12 @@ private DimensionList parseDefaultDimensions(Map<String, String> defaultDimensio
* metric lines.
*/
@Override
public void export(List<Meter> meters) {
public void export(@NonNull 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 +224,9 @@ private String createGaugeLine(Meter meter, Map<String, String> seenMetadata, Me
meter.getId().getName()));
return null;
}
Metric.Builder metricBuilder = createMetricBuilder(meter).setDoubleGaugeValue(value);

storeMetadataLine(metricBuilder, seenMetadata);

return metricBuilder.serializeMetricLine();
MetricLineBuilder.GaugeStep gaugeStep = createTypeStep(meter).gauge();
storeMetadata(enrichMetadata(gaugeStep.metadata(), meter), seenMetadata);
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,9 @@ 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());

storeMetadataLine(metricBuilder, seenMetadata);

return metricBuilder.serializeMetricLine();
MetricLineBuilder.CounterStep counterStep = createTypeStep(meter).count();
storeMetadata(enrichMetadata(counterStep.metadata(), meter), seenMetadata);
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 +295,11 @@ 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);

storeMetadataLine(builder, seenMetadata);

return Stream.of(builder.serializeMetricLine());
MetricLineBuilder.GaugeStep gaugeStep = createTypeStep(meter).gauge();
storeMetadata(enrichMetadata(gaugeStep.metadata(), meter), seenMetadata);
return Stream.of(gaugeStep.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 +385,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 +460,47 @@ private void handleSuccess(int totalSent, HttpSender.Response response) {
}
}

private void storeMetadataLine(Metric.Builder metricBuilder, Map<String, String> seenMetadata)
throws MetricException {
// if the config to export metadata is turned off, the seenMetadata map will be
// null.
if (seenMetadata == null) {
private MetricLineBuilder.MetadataStep enrichMetadata(MetricLineBuilder.MetadataStep metadataStep, Meter meter) {
return metadataStep.description(meter.getId().getDescription()).unit(meter.getId().getBaseUnit());
}

/**
* Adds metadata found in {@link MetadataStep} to the {@code seenMetadata}
* @param metadataStep source of the metadata that should be added to
* {@code seenMetadata}
* @param seenMetadata destination of the metadata
*/
private void storeMetadata(MetricLineBuilder.MetadataStep metadataStep, Map<String, String> seenMetadata) {
// if the config to export metadata is turned off, seenMetadata will be null
if (seenMetadata == null || metadataStep == null) {
return;
}

String key = metricBuilder.getNormalizedMetricKey();
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 +510,23 @@ private void storeMetadataLine(Metric.Builder metricBuilder, Map<String, String>
}
}

private String extractMetricKey(String metadataLine) {
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);
}

}
Loading

0 comments on commit 8d18d8c

Please sign in to comment.