Skip to content

Commit 37820ef

Browse files
committed
extract tranformation set
1 parent 8b807b9 commit 37820ef

File tree

2 files changed

+172
-122
lines changed

2 files changed

+172
-122
lines changed

src/main/java/com/arpnetworking/metrics/mad/sources/TransformingSource.java

Lines changed: 162 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void stop() {
7878
public Object toLogValue() {
7979
return LogValueMapFactory.builder(this)
8080
.put("source", _source)
81-
.put("findAndReplace", _findAndReplace)
81+
.put("tranformations", _transformations)
8282
.build();
8383
}
8484

@@ -90,24 +90,13 @@ public String toString() {
9090
private TransformingSource(final Builder builder) {
9191
super(builder);
9292
_source = builder._source;
93+
_transformations = builder._transformations;
9394

94-
final ImmutableMap.Builder<Pattern, ImmutableList<String>> findReplaceBuilder =
95-
ImmutableMap.builderWithExpectedSize(builder._findAndReplace.size());
96-
for (final ImmutableMap.Entry<String, ? extends ImmutableList<String>> entry : builder._findAndReplace.entrySet()) {
97-
findReplaceBuilder.put(Pattern.compile(entry.getKey()), ImmutableList.copyOf(entry.getValue()));
98-
}
99-
_findAndReplace = findReplaceBuilder.build();
100-
101-
_inject = builder._inject;
102-
_remove = builder._remove;
103-
104-
_source.attach(new TransformingObserver(this, _findAndReplace, _inject, _remove));
95+
_source.attach(new TransformingObserver(this, _transformations));
10596
}
10697

10798
private final Source _source;
108-
private final ImmutableMap<Pattern, ImmutableList<String>> _findAndReplace;
109-
private final ImmutableMap<String, DimensionInjection> _inject;
110-
private final ImmutableList<String> _remove;
99+
private final ImmutableList<TransformationSet> _transformations;
111100

112101
private static final Logger LOGGER = LoggerFactory.getLogger(TransformingSource.class);
113102
private static final Splitter.MapSplitter TAG_SPLITTER = Splitter.on(';').omitEmptyStrings().trimResults().withKeyValueSeparator('=');
@@ -117,13 +106,9 @@ private TransformingSource(final Builder builder) {
117106

118107
/* package private */ TransformingObserver(
119108
final TransformingSource source,
120-
final ImmutableMap<Pattern, ImmutableList<String>> findAndReplace,
121-
final ImmutableMap<String, DimensionInjection> inject,
122-
final ImmutableList<String> remove) {
109+
final ImmutableList<TransformationSet> transformations) {
123110
_source = source;
124-
_findAndReplace = findAndReplace;
125-
_inject = inject;
126-
_remove = remove;
111+
_transformations = transformations;
127112
}
128113

129114
@Override
@@ -139,79 +124,86 @@ public void notify(final Observable observable, final Object event) {
139124
// Merge the metrics in the record together
140125
final Record record = (Record) event;
141126
final Map<Key, Map<String, MergingMetric>> mergedMetrics = Maps.newHashMap();
142-
for (final Map.Entry<String, ? extends Metric> metric : record.getMetrics().entrySet()) {
143-
boolean found = false;
144-
final String metricName = metric.getKey();
145-
for (final Map.Entry<Pattern, ImmutableList<String>> findAndReplace : _findAndReplace.entrySet()) {
146-
final Pattern metricPattern = findAndReplace.getKey();
147-
final Matcher matcher = metricPattern.matcher(metricName);
148-
if (matcher.find()) {
149-
for (final String replacement : findAndReplace.getValue()) {
150-
final RegexAndMapReplacer.Replacement rep =
151-
RegexAndMapReplacer.replaceAll(metricPattern, metricName, replacement, record.getDimensions());
152-
final String replacedString = rep.getReplacement();
153-
final ImmutableList<String> consumedDimensions = rep.getVariablesMatched();
154-
155-
final int tagsStart = replacedString.indexOf(';');
156-
if (tagsStart == -1) {
157-
// We just have a metric name. Optimize for this common case
158-
merge(
159-
metric.getValue(),
160-
replacedString,
161-
mergedMetrics,
162-
getModifiedDimensions(record.getDimensions(), Collections.emptyMap(), consumedDimensions));
163-
} else {
164-
final String newMetricName = replacedString.substring(0, tagsStart);
165-
final Map<String, String> parsedTags = TAG_SPLITTER.split(replacedString.substring(tagsStart + 1));
166-
merge(
167-
metric.getValue(),
168-
newMetricName,
169-
mergedMetrics,
170-
getModifiedDimensions(record.getDimensions(), parsedTags, consumedDimensions));
127+
for (TransformationSet transformation : _transformations) {
128+
for (final Map.Entry<String, ? extends Metric> metric : record.getMetrics().entrySet()) {
129+
boolean found = false;
130+
final String metricName = metric.getKey();
131+
for (final Map.Entry<Pattern, ImmutableList<String>> findAndReplace : transformation.getFindAndReplace().entrySet()) {
132+
final Pattern metricPattern = findAndReplace.getKey();
133+
final Matcher matcher = metricPattern.matcher(metricName);
134+
if (matcher.find()) {
135+
for (final String replacement : findAndReplace.getValue()) {
136+
final RegexAndMapReplacer.Replacement rep =
137+
RegexAndMapReplacer.replaceAll(metricPattern, metricName, replacement, record.getDimensions());
138+
final String replacedString = rep.getReplacement();
139+
final ImmutableList<String> consumedDimensions = rep.getVariablesMatched();
140+
141+
final int tagsStart = replacedString.indexOf(';');
142+
if (tagsStart == -1) {
143+
// We just have a metric name. Optimize for this common case
144+
merge(
145+
metric.getValue(),
146+
replacedString,
147+
mergedMetrics,
148+
getModifiedDimensions(
149+
record.getDimensions(),
150+
Collections.emptyMap(),
151+
consumedDimensions,
152+
transformation));
153+
} else {
154+
final String newMetricName = replacedString.substring(0, tagsStart);
155+
final Map<String, String> parsedTags = TAG_SPLITTER.split(replacedString.substring(tagsStart + 1));
156+
merge(
157+
metric.getValue(),
158+
newMetricName,
159+
mergedMetrics,
160+
getModifiedDimensions(record.getDimensions(), parsedTags, consumedDimensions, transformation));
161+
}
171162
}
163+
//Having "found" set here means that mapping a metric to an empty list suppresses that metric
164+
found = true;
172165
}
173-
//Having "found" set here means that mapping a metric to an empty list suppresses that metric
174-
found = true;
166+
}
167+
if (!found) {
168+
merge(
169+
metric.getValue(),
170+
metricName,
171+
mergedMetrics,
172+
getModifiedDimensions(record.getDimensions(), Collections.emptyMap(), ImmutableList.of(), transformation));
175173
}
176174
}
177-
if (!found) {
178-
merge(
179-
metric.getValue(),
180-
metricName,
181-
mergedMetrics,
182-
getModifiedDimensions(record.getDimensions(), Collections.emptyMap(), ImmutableList.of()));
183-
}
184-
}
185175

186-
// Raise the merged record event with this source's observers
187-
// NOTE: Do not leak instances of MergingMetric since it is mutable
188-
for (final Map.Entry<Key, Map<String, MergingMetric>> entry : mergedMetrics.entrySet()) {
189-
_source.notify(
190-
ThreadLocalBuilder.build(
191-
DefaultRecord.Builder.class,
192-
b1 -> b1.setMetrics(
193-
entry.getValue().entrySet().stream().collect(
194-
ImmutableMap.toImmutableMap(
195-
Map.Entry::getKey,
196-
e -> ThreadLocalBuilder.clone(
197-
e.getValue(),
198-
DefaultMetric.Builder.class))))
199-
.setId(record.getId())
200-
.setTime(record.getTime())
201-
.setAnnotations(record.getAnnotations())
202-
.setDimensions(entry.getKey().getParameters())));
176+
// Raise the merged record event with this source's observers
177+
// NOTE: Do not leak instances of MergingMetric since it is mutable
178+
for (final Map.Entry<Key, Map<String, MergingMetric>> entry : mergedMetrics.entrySet()) {
179+
_source.notify(
180+
ThreadLocalBuilder.build(
181+
DefaultRecord.Builder.class,
182+
b1 -> b1.setMetrics(
183+
entry.getValue().entrySet().stream().collect(
184+
ImmutableMap.toImmutableMap(
185+
Map.Entry::getKey,
186+
e -> ThreadLocalBuilder.clone(
187+
e.getValue(),
188+
DefaultMetric.Builder.class))))
189+
.setId(record.getId())
190+
.setTime(record.getTime())
191+
.setAnnotations(record.getAnnotations())
192+
.setDimensions(entry.getKey().getParameters())));
193+
}
203194
}
204195
}
205196

206197
private Key getModifiedDimensions(
207198
final ImmutableMap<String, String> inputDimensions,
208199
final Map<String, String> add,
209-
final ImmutableList<String> remove) {
200+
final ImmutableList<String> remove,
201+
final TransformationSet transformation) {
210202
final Map<String, String> finalTags = Maps.newHashMap(inputDimensions);
211203
// Remove the dimensions that we consumed in the replacement
212204
remove.forEach(finalTags::remove);
213-
_remove.forEach(finalTags::remove);
214-
_inject.forEach(
205+
transformation.getRemove().forEach(finalTags::remove);
206+
transformation.getInject().forEach(
215207
(key, inject) ->
216208
finalTags.compute(key, (k, oldValue) ->
217209
inject.isReplaceExisting() || oldValue == null ? inject.getValue() : oldValue));
@@ -247,9 +239,7 @@ private void merge(
247239
}
248240

249241
private final TransformingSource _source;
250-
private final ImmutableMap<Pattern, ImmutableList<String>> _findAndReplace;
251-
private final ImmutableMap<String, DimensionInjection> _inject;
252-
private final ImmutableList<String> _remove;
242+
private final ImmutableList<TransformationSet> _transformations;
253243
}
254244

255245
// NOTE: Package private for testing
@@ -357,6 +347,87 @@ public Builder setReplaceExisting(final Boolean value) {
357347
}
358348
}
359349

350+
public static final class TransformationSet {
351+
public ImmutableMap<Pattern, ImmutableList<String>> getFindAndReplace() {
352+
return _findAndReplace;
353+
}
354+
355+
public ImmutableMap<String, DimensionInjection> getInject() {
356+
return _inject;
357+
}
358+
359+
public ImmutableList<String> getRemove() {
360+
return _remove;
361+
}
362+
363+
private TransformationSet(final Builder builder) {
364+
final ImmutableMap.Builder<Pattern, ImmutableList<String>> findReplaceBuilder =
365+
ImmutableMap.builderWithExpectedSize(builder._findAndReplace.size());
366+
for (final ImmutableMap.Entry<String, ? extends ImmutableList<String>> entry : builder._findAndReplace.entrySet()) {
367+
findReplaceBuilder.put(Pattern.compile(entry.getKey()), ImmutableList.copyOf(entry.getValue()));
368+
}
369+
_findAndReplace = findReplaceBuilder.build();
370+
_inject = builder._inject;
371+
_remove = builder._remove;
372+
}
373+
374+
private final ImmutableMap<String, DimensionInjection> _inject;
375+
private final ImmutableList<String> _remove;
376+
private final ImmutableMap<Pattern, ImmutableList<String>> _findAndReplace;
377+
378+
/**
379+
* Implementation of the builder pattern for a {@link TransformationSet}.
380+
*/
381+
public static final class Builder extends OvalBuilder<TransformationSet> {
382+
/**
383+
* Public constructor.
384+
*/
385+
public Builder() {
386+
super(TransformationSet::new);
387+
}
388+
389+
/**
390+
* Sets find and replace expression map. Optional. Cannot be null. Defaults to empty.
391+
*
392+
* @param value The find and replace expression map.
393+
* @return This instance of <code>Builder</code>.
394+
*/
395+
public Builder setFindAndReplace(final ImmutableMap<String, ? extends ImmutableList<String>> value) {
396+
_findAndReplace = value;
397+
return this;
398+
}
399+
400+
/**
401+
* Sets dimensions to inject. Optional. Cannot be null. Defaults to empty.
402+
*
403+
* @param value List of dimensions to inject.
404+
* @return This instance of <code>Builder</code>.
405+
*/
406+
public Builder setInject(final ImmutableMap<String, DimensionInjection> value) {
407+
_inject = value;
408+
return this;
409+
}
410+
411+
/**
412+
* Sets dimensions to remove. Optional. Cannot be null. Defaults to empty.
413+
*
414+
* @param value List of dimensions to inject.
415+
* @return This instance of <code>Builder</code>.
416+
*/
417+
public Builder setRemove(final ImmutableList<String> value) {
418+
_remove = value;
419+
return this;
420+
}
421+
422+
@NotNull
423+
private ImmutableMap<String, ? extends ImmutableList<String>> _findAndReplace = ImmutableMap.of();
424+
@NotNull
425+
private ImmutableMap<String, DimensionInjection> _inject = ImmutableMap.of();
426+
@NotNull
427+
private ImmutableList<String> _remove = ImmutableList.of();
428+
}
429+
}
430+
360431
/**
361432
* Implementation of builder pattern for {@link TransformingSource}.
362433
*
@@ -375,43 +446,21 @@ public Builder() {
375446
* Sets the underlying source. Cannot be null.
376447
*
377448
* @param value The underlying source.
378-
* @return This instance of <code>Builder</code>.
449+
* @return This instance of {@link Builder}.
379450
*/
380451
public Builder setSource(final Source value) {
381452
_source = value;
382453
return this;
383454
}
384455

385456
/**
386-
* Sets find and replace expression map. Optional. Cannot be null. Defaults to empty.
387-
*
388-
* @param value The find and replace expression map.
389-
* @return This instance of <code>Builder</code>.
390-
*/
391-
public Builder setFindAndReplace(final ImmutableMap<String, ? extends ImmutableList<String>> value) {
392-
_findAndReplace = value;
393-
return this;
394-
}
395-
396-
/**
397-
* Sets dimensions to inject. Optional. Cannot be null. Defaults to empty.
457+
* Sets the transformations. Required. Cannot be null. Cannot be empty.
398458
*
399-
* @param value List of dimensions to inject.
400-
* @return This instance of <code>Builder</code>.
459+
* @param value The list of transformations to apply.
460+
* @return This instance of {@link Builder}.
401461
*/
402-
public Builder setInject(final ImmutableMap<String, DimensionInjection> value) {
403-
_inject = value;
404-
return this;
405-
}
406-
407-
/**
408-
* Sets dimensions to remove. Optional. Cannot be null. Defaults to empty.
409-
*
410-
* @param value List of dimensions to inject.
411-
* @return This instance of <code>Builder</code>.
412-
*/
413-
public Builder setRemove(final ImmutableList<String> value) {
414-
_remove = value;
462+
public Builder setTransformations(final ImmutableList<TransformationSet> value) {
463+
_transformations = value;
415464
return this;
416465
}
417466

@@ -423,10 +472,7 @@ protected Builder self() {
423472
@NotNull
424473
private Source _source;
425474
@NotNull
426-
private ImmutableMap<String, ? extends ImmutableList<String>> _findAndReplace = ImmutableMap.of();
427-
@NotNull
428-
private ImmutableMap<String, DimensionInjection> _inject = ImmutableMap.of();
429-
@NotNull
430-
private ImmutableList<String> _remove = ImmutableList.of();
475+
@NotEmpty
476+
private ImmutableList<TransformationSet> _transformations;
431477
}
432478
}

src/test/java/com/arpnetworking/metrics/mad/sources/TransformingSourceTest.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@ public void setUp() {
5050
_mockSource = Mockito.mock(Source.class);
5151
_transformingSourceBuilder = new TransformingSource.Builder()
5252
.setName("TransformingSourceTest")
53-
.setFindAndReplace(ImmutableMap.of(
54-
"foo/([^/]*)/bar", ImmutableList.of("foo/bar"),
55-
"cat/([^/]*)/dog", ImmutableList.of("cat/dog", "cat/dog/$1"),
56-
"tagged/([^/]*)/dog", ImmutableList.of("tagged/dog;animal=$1"),
57-
"named/(?<animal>[^/]*)", ImmutableList.of("named/extracted_animal;extracted=${animal}"),
58-
"tagged/([^/]*)/animal", ImmutableList.of("tagged/${animal}/animal")))
53+
.setTransformations(ImmutableList.of(
54+
new TransformingSource.TransformationSet.Builder()
55+
.setFindAndReplace(ImmutableMap.of(
56+
"foo/([^/]*)/bar", ImmutableList.of("foo/bar"),
57+
"cat/([^/]*)/dog", ImmutableList.of("cat/dog", "cat/dog/$1"),
58+
"tagged/([^/]*)/dog", ImmutableList.of("tagged/dog;animal=$1"),
59+
"named/(?<animal>[^/]*)", ImmutableList.of("named/extracted_animal;extracted=${animal}"),
60+
"tagged/([^/]*)/animal", ImmutableList.of("tagged/${animal}/animal")))
61+
.build()
62+
))
5963
.setSource(_mockSource);
6064
}
6165

0 commit comments

Comments
 (0)