-
Notifications
You must be signed in to change notification settings - Fork 961
/
DynatraceMeterRegistryTest.java
331 lines (292 loc) · 14.1 KB
/
DynatraceMeterRegistryTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/**
* Copyright 2017 Pivotal Software, Inc.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.dynatrace;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.config.MissingRequiredConfigurationException;
import io.micrometer.core.ipc.http.HttpSender;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link DynatraceMeterRegistry}.
*
* @author Johnny Lim
*/
class DynatraceMeterRegistryTest {
private final DynatraceMeterRegistry meterRegistry = createMeterRegistry();
private final ObjectMapper mapper = new ObjectMapper();
@Test
void constructorWhenUriIsMissingShouldThrowMissingRequiredConfigurationException() {
assertThatThrownBy(() -> new DynatraceMeterRegistry(new DynatraceConfig() {
@Override
public String get(String key) {
return null;
}
@Override
public String deviceId() {
return "deviceId";
}
@Override
public String apiToken() {
return "apiToken";
}
}, Clock.SYSTEM))
.isExactlyInstanceOf(MissingRequiredConfigurationException.class)
.hasMessage("uri must be set to report metrics to Dynatrace");
}
@Test
void constructorWhenDeviceIdIsMissingShouldThrowMissingRequiredConfigurationException() {
assertThatThrownBy(() -> new DynatraceMeterRegistry(new DynatraceConfig() {
@Override
public String get(String key) {
return null;
}
@Override
public String uri() {
return "uri";
}
@Override
public String apiToken() {
return "apiToken";
}
}, Clock.SYSTEM))
.isExactlyInstanceOf(MissingRequiredConfigurationException.class)
.hasMessage("deviceId must be set to report metrics to Dynatrace");
}
@Test
void constructorWhenApiTokenIsMissingShouldThrowMissingRequiredConfigurationException() {
assertThatThrownBy(() -> new DynatraceMeterRegistry(new DynatraceConfig() {
@Override
public String get(String key) {
return null;
}
@Override
public String uri() {
return "uri";
}
@Override
public String deviceId() {
return "deviceId";
}
}, Clock.SYSTEM))
.isExactlyInstanceOf(MissingRequiredConfigurationException.class)
.hasMessage("apiToken must be set to report metrics to Dynatrace");
}
@Test
void putCustomMetricOnSuccessShouldAddMetricIdToCreatedCustomMetrics() throws NoSuchFieldException, IllegalAccessException {
Field createdCustomMetricsField = DynatraceMeterRegistry.class.getDeclaredField("createdCustomMetrics");
createdCustomMetricsField.setAccessible(true);
@SuppressWarnings("unchecked")
Set<String> createdCustomMetrics = (Set<String>) createdCustomMetricsField.get(meterRegistry);
assertThat(createdCustomMetrics).isEmpty();
DynatraceMetricDefinition customMetric = new DynatraceMetricDefinition("metricId", null, null, null, new String[]{"type"}, null);
meterRegistry.putCustomMetric(customMetric);
assertThat(createdCustomMetrics).containsExactly("metricId");
}
@Test
void writeMeterWithGauge() {
meterRegistry.gauge("my.gauge", 1d);
Gauge gauge = meterRegistry.find("my.gauge").gauge();
assertThat(meterRegistry.writeMeter(gauge)).hasSize(1);
}
@Test
void writeMeterWithGaugeShouldDropNanValue() {
meterRegistry.gauge("my.gauge", Double.NaN);
Gauge gauge = meterRegistry.find("my.gauge").gauge();
assertThat(meterRegistry.writeMeter(gauge)).isEmpty();
}
@SuppressWarnings("unchecked")
@Test
void writeMeterWithGaugeWhenChangingFiniteToNaNShouldWork() {
AtomicBoolean first = new AtomicBoolean(true);
meterRegistry.gauge("my.gauge", first, (b) -> b.getAndSet(false) ? 1d : Double.NaN);
Gauge gauge = meterRegistry.find("my.gauge").gauge();
Stream<DynatraceMeterRegistry.DynatraceCustomMetric> stream = meterRegistry.writeMeter(gauge);
List<DynatraceMeterRegistry.DynatraceCustomMetric> metrics = stream.collect(Collectors.toList());
assertThat(metrics).hasSize(1);
DynatraceMeterRegistry.DynatraceCustomMetric metric = metrics.get(0);
DynatraceTimeSeries timeSeries = metric.getTimeSeries();
try {
Map<String, Object> map = mapper.readValue(timeSeries.asJson(), Map.class);
List<List<Number>> dataPoints = (List<List<Number>>) map.get("dataPoints");
assertThat(dataPoints.get(0).get(1).doubleValue()).isEqualTo(1d);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Test
void writeMeterWithGaugeShouldDropInfiniteValues() {
meterRegistry.gauge("my.gauge", Double.POSITIVE_INFINITY);
Gauge gauge = meterRegistry.find("my.gauge").gauge();
assertThat(meterRegistry.writeMeter(gauge)).isEmpty();
meterRegistry.gauge("my.gauge", Double.NEGATIVE_INFINITY);
gauge = meterRegistry.find("my.gauge").gauge();
assertThat(meterRegistry.writeMeter(gauge)).isEmpty();
}
@Test
void writeMeterWithTimeGauge() {
AtomicReference<Double> obj = new AtomicReference<>(1d);
meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
assertThat(meterRegistry.writeMeter(timeGauge)).hasSize(1);
}
@Test
void writeMeterWithTimeGaugeShouldDropNanValue() {
AtomicReference<Double> obj = new AtomicReference<>(Double.NaN);
meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
assertThat(meterRegistry.writeMeter(timeGauge)).isEmpty();
}
@Test
void writeMeterWithTimeGaugeShouldDropInfiniteValues() {
AtomicReference<Double> obj = new AtomicReference<>(Double.POSITIVE_INFINITY);
meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
TimeGauge timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
assertThat(meterRegistry.writeMeter(timeGauge)).isEmpty();
obj = new AtomicReference<>(Double.NEGATIVE_INFINITY);
meterRegistry.more().timeGauge("my.timeGauge", Tags.empty(), obj, TimeUnit.SECONDS, AtomicReference::get);
timeGauge = meterRegistry.find("my.timeGauge").timeGauge();
assertThat(meterRegistry.writeMeter(timeGauge)).isEmpty();
}
@Test
void writeCustomMetrics() {
Double number = 1d;
meterRegistry.gauge("my.gauge", number);
Gauge gauge = meterRegistry.find("my.gauge").gauge();
Stream<DynatraceMeterRegistry.DynatraceCustomMetric> series = meterRegistry.writeMeter(gauge);
List<DynatraceTimeSeries> timeSeries = series
.map(DynatraceMeterRegistry.DynatraceCustomMetric::getTimeSeries)
.collect(Collectors.toList());
List<DynatraceBatchedPayload> entries = meterRegistry.createPostMessages("my.type", null, timeSeries);
assertThat(entries).hasSize(1);
assertThat(entries.get(0).metricCount).isEqualTo(1);
assertThat(isValidJson(entries.get(0).payload)).isEqualTo(true);
}
@Test
void whenAllTsTooLargeEmptyMessageListReturned() {
List<DynatraceBatchedPayload> messages = meterRegistry.createPostMessages("my.type", null, Collections.singletonList(createTimeSeriesWithDimensions(10_000)));
assertThat(messages).isEmpty();
}
@Test
void splitsWhenExactlyExceedingMaxByComma() {
// comma needs to be considered when there is more than one time series
List<DynatraceBatchedPayload> messages = meterRegistry.createPostMessages("my.type", "my.group",
// Max bytes: 15330 (excluding header/footer, 15360 with header/footer)
Arrays.asList(createTimeSeriesWithDimensions(750), // 14861 bytes
createTimeSeriesWithDimensions(23, "asdfg"), // 469 bytes (overflows due to comma)
createTimeSeriesWithDimensions(750), // 14861 bytes
createTimeSeriesWithDimensions(22, "asd") // 468 bytes + comma
));
assertThat(messages).hasSize(3);
assertThat(messages.get(0).metricCount).isEqualTo(1);
assertThat(messages.get(1).metricCount).isEqualTo(1);
assertThat(messages.get(2).metricCount).isEqualTo(2);
assertThat(messages.get(2).payload.getBytes(UTF_8).length).isEqualTo(15360);
assertThat(messages.stream().map(message -> message.payload).allMatch(this::isValidJson)).isTrue();
}
@Test
void countsPreviousAndNextComma() {
List<DynatraceBatchedPayload> messages = meterRegistry.createPostMessages("my.type", null,
// Max bytes: 15330 (excluding header/footer, 15360 with header/footer)
Arrays.asList(createTimeSeriesWithDimensions(750), // 14861 bytes
createTimeSeriesWithDimensions(10, "asdf"), // 234 bytes + comma
createTimeSeriesWithDimensions(10, "asdf") // 234 bytes + comma = 15331 bytes (overflow)
));
assertThat(messages).hasSize(2);
assertThat(messages.get(0).metricCount).isEqualTo(2);
assertThat(messages.get(1).metricCount).isEqualTo(1);
assertThat(messages.stream().map(message -> message.payload).allMatch(this::isValidJson)).isTrue();
}
@Test
void writeMeterWhenCustomMeterHasOnlyNonFiniteValuesShouldNotBeWritten() {
Measurement measurement1 = new Measurement(() -> Double.POSITIVE_INFINITY, Statistic.VALUE);
Measurement measurement2 = new Measurement(() -> Double.NEGATIVE_INFINITY, Statistic.VALUE);
Measurement measurement3 = new Measurement(() -> Double.NaN, Statistic.VALUE);
List<Measurement> measurements = Arrays.asList(measurement1, measurement2, measurement3);
Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(this.meterRegistry);
assertThat(meterRegistry.writeMeter(meter)).isEmpty();
}
@Test
void writeMeterWhenCustomMeterHasMixedFiniteAndNonFiniteValuesShouldSkipOnlyNonFiniteValues() {
Measurement measurement1 = new Measurement(() -> Double.POSITIVE_INFINITY, Statistic.VALUE);
Measurement measurement2 = new Measurement(() -> Double.NEGATIVE_INFINITY, Statistic.VALUE);
Measurement measurement3 = new Measurement(() -> Double.NaN, Statistic.VALUE);
Measurement measurement4 = new Measurement(() -> 1d, Statistic.VALUE);
Measurement measurement5 = new Measurement(() -> 2d, Statistic.VALUE);
List<Measurement> measurements = Arrays.asList(measurement1, measurement2, measurement3, measurement4, measurement5);
Meter meter = Meter.builder("my.meter", Meter.Type.GAUGE, measurements).register(this.meterRegistry);
assertThat(meterRegistry.writeMeter(meter)).hasSize(2);
}
private DynatraceTimeSeries createTimeSeriesWithDimensions(int numberOfDimensions) {
return createTimeSeriesWithDimensions(numberOfDimensions, "some.metric");
}
private DynatraceTimeSeries createTimeSeriesWithDimensions(int numberOfDimensions, String metricId) {
return new DynatraceTimeSeries(metricId, System.currentTimeMillis(), 1.23, createDimensionsMap(numberOfDimensions));
}
private Map<String, String> createDimensionsMap(int numberOfDimensions) {
Map<String, String> map = new HashMap<>();
IntStream.range(0, numberOfDimensions).forEach(i -> map.put("key" + i, "value" + i));
return map;
}
private DynatraceMeterRegistry createMeterRegistry() {
DynatraceConfig config = new DynatraceConfig() {
@Override
public String get(String key) {
return null;
}
@Override
public String uri() {
return "http://localhost";
}
@Override
public String deviceId() {
return "deviceId";
}
@Override
public String apiToken() {
return "apiToken";
}
};
return DynatraceMeterRegistry.builder(config)
.httpClient(request -> new HttpSender.Response(200, null))
.build();
}
private boolean isValidJson(String json) {
try {
mapper.readTree(json);
return true;
} catch (Exception e) {
return false;
}
}
}