Skip to content

Commit 90ce3a6

Browse files
polyfractaljimczi
authored andcommitted
[Rollup] Fix Caps Comparator to handle calendar/fixed time (#33336)
The comparator used TimeValue parsing, which meant it couldn't handle calendar time. This fixes the comparator to handle either (and potentially mixed). The mixing shouldn't be an issue since the validation code upstream will prevent it, but was simplest to allow the comparator to handle both.
1 parent 9310d2e commit 90ce3a6

File tree

3 files changed

+160
-38
lines changed

3 files changed

+160
-38
lines changed

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/rollup/job/DateHistogramGroupConfigSerializingTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ public void testValidateMatchingField() {
125125
assertThat(e.validationErrors().size(), equalTo(0));
126126
}
127127

128+
public void testValidateWeek() {
129+
ActionRequestValidationException e = new ActionRequestValidationException();
130+
Map<String, Map<String, FieldCapabilities>> responseMap = new HashMap<>();
131+
132+
// Have to mock fieldcaps because the ctor's aren't public...
133+
FieldCapabilities fieldCaps = mock(FieldCapabilities.class);
134+
when(fieldCaps.isAggregatable()).thenReturn(true);
135+
responseMap.put("my_field", Collections.singletonMap("date", fieldCaps));
136+
137+
DateHistogramGroupConfig config = new DateHistogramGroupConfig("my_field", new DateHistogramInterval("1w"), null, null);
138+
config.validateMappings(responseMap, e);
139+
assertThat(e.validationErrors().size(), equalTo(0));
140+
}
141+
128142
/**
129143
* Tests that a DateHistogramGroupConfig can be serialized/deserialized correctly after
130144
* the timezone was changed from DateTimeZone to String.

x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/RollupJobIdentifierUtils.java

+22-38
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package org.elasticsearch.xpack.rollup;
77

8+
import org.elasticsearch.common.rounding.DateTimeUnit;
89
import org.elasticsearch.common.unit.TimeValue;
910
import org.elasticsearch.search.aggregations.AggregationBuilder;
1011
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
@@ -18,9 +19,7 @@
1819
import org.joda.time.DateTimeZone;
1920

2021
import java.util.ArrayList;
21-
import java.util.Collections;
2222
import java.util.Comparator;
23-
import java.util.HashMap;
2423
import java.util.HashSet;
2524
import java.util.List;
2625
import java.util.Map;
@@ -33,30 +32,7 @@
3332
*/
3433
public class RollupJobIdentifierUtils {
3534

36-
private static final Comparator<RollupJobCaps> COMPARATOR = RollupJobIdentifierUtils.getComparator();
37-
38-
public static final Map<String, Integer> CALENDAR_ORDERING;
39-
40-
static {
41-
Map<String, Integer> dateFieldUnits = new HashMap<>(16);
42-
dateFieldUnits.put("year", 8);
43-
dateFieldUnits.put("1y", 8);
44-
dateFieldUnits.put("quarter", 7);
45-
dateFieldUnits.put("1q", 7);
46-
dateFieldUnits.put("month", 6);
47-
dateFieldUnits.put("1M", 6);
48-
dateFieldUnits.put("week", 5);
49-
dateFieldUnits.put("1w", 5);
50-
dateFieldUnits.put("day", 4);
51-
dateFieldUnits.put("1d", 4);
52-
dateFieldUnits.put("hour", 3);
53-
dateFieldUnits.put("1h", 3);
54-
dateFieldUnits.put("minute", 2);
55-
dateFieldUnits.put("1m", 2);
56-
dateFieldUnits.put("second", 1);
57-
dateFieldUnits.put("1s", 1);
58-
CALENDAR_ORDERING = Collections.unmodifiableMap(dateFieldUnits);
59-
}
35+
static final Comparator<RollupJobCaps> COMPARATOR = RollupJobIdentifierUtils.getComparator();
6036

6137
/**
6238
* Given the aggregation tree and a list of available job capabilities, this method will return a set
@@ -176,8 +152,10 @@ static boolean validateCalendarInterval(DateHistogramInterval requestInterval,
176152

177153
// The request must be gte the config. The CALENDAR_ORDERING map values are integers representing
178154
// relative orders between the calendar units
179-
int requestOrder = CALENDAR_ORDERING.getOrDefault(requestInterval.toString(), Integer.MAX_VALUE);
180-
int configOrder = CALENDAR_ORDERING.getOrDefault(configInterval.toString(), Integer.MAX_VALUE);
155+
DateTimeUnit requestUnit = DateHistogramAggregationBuilder.DATE_FIELD_UNITS.get(requestInterval.toString());
156+
long requestOrder = requestUnit.field(DateTimeZone.UTC).getDurationField().getUnitMillis();
157+
DateTimeUnit configUnit = DateHistogramAggregationBuilder.DATE_FIELD_UNITS.get(configInterval.toString());
158+
long configOrder = configUnit.field(DateTimeZone.UTC).getDurationField().getUnitMillis();
181159

182160
// All calendar units are multiples naturally, so we just care about gte
183161
return requestOrder >= configOrder;
@@ -190,7 +168,7 @@ static boolean validateFixedInterval(DateHistogramInterval requestInterval,
190168
return false;
191169
}
192170

193-
// Both are fixed, good to conver to millis now
171+
// Both are fixed, good to convert to millis now
194172
long configIntervalMillis = TimeValue.parseTimeValue(configInterval.toString(),
195173
"date_histo.config.interval").getMillis();
196174
long requestIntervalMillis = TimeValue.parseTimeValue(requestInterval.toString(),
@@ -326,8 +304,8 @@ private static Comparator<RollupJobCaps> getComparator() {
326304
return 0;
327305
}
328306

329-
TimeValue thisTime = null;
330-
TimeValue thatTime = null;
307+
long thisTime = Long.MAX_VALUE;
308+
long thatTime = Long.MAX_VALUE;
331309

332310
// histogram intervals are averaged and compared, with the idea that
333311
// a larger average == better, because it will generate fewer documents
@@ -344,7 +322,7 @@ private static Comparator<RollupJobCaps> getComparator() {
344322
for (RollupJobCaps.RollupFieldCaps fieldCaps : o1.getFieldCaps().values()) {
345323
for (Map<String, Object> agg : fieldCaps.getAggs()) {
346324
if (agg.get(RollupField.AGG).equals(DateHistogramAggregationBuilder.NAME)) {
347-
thisTime = TimeValue.parseTimeValue((String) agg.get(RollupField.INTERVAL), RollupField.INTERVAL);
325+
thisTime = getMillisFixedOrCalendar((String) agg.get(RollupField.INTERVAL));
348326
} else if (agg.get(RollupField.AGG).equals(HistogramAggregationBuilder.NAME)) {
349327
thisHistoWeights += (long) agg.get(RollupField.INTERVAL);
350328
counter += 1;
@@ -360,7 +338,7 @@ private static Comparator<RollupJobCaps> getComparator() {
360338
for (RollupJobCaps.RollupFieldCaps fieldCaps : o2.getFieldCaps().values()) {
361339
for (Map<String, Object> agg : fieldCaps.getAggs()) {
362340
if (agg.get(RollupField.AGG).equals(DateHistogramAggregationBuilder.NAME)) {
363-
thatTime = TimeValue.parseTimeValue((String) agg.get(RollupField.INTERVAL), RollupField.INTERVAL);
341+
thatTime = getMillisFixedOrCalendar((String) agg.get(RollupField.INTERVAL));
364342
} else if (agg.get(RollupField.AGG).equals(HistogramAggregationBuilder.NAME)) {
365343
thatHistoWeights += (long) agg.get(RollupField.INTERVAL);
366344
counter += 1;
@@ -371,13 +349,9 @@ private static Comparator<RollupJobCaps> getComparator() {
371349
}
372350
thatHistoWeights = counter == 0 ? 0 : thatHistoWeights / counter;
373351

374-
// DateHistos are mandatory so these should always be present no matter what
375-
assert thisTime != null;
376-
assert thatTime != null;
377-
378352
// Compare on date interval first
379353
// The "smaller" job is the one with the larger interval
380-
int timeCompare = thisTime.compareTo(thatTime);
354+
int timeCompare = Long.compare(thisTime, thatTime);
381355
if (timeCompare != 0) {
382356
return -timeCompare;
383357
}
@@ -409,4 +383,14 @@ private static Comparator<RollupJobCaps> getComparator() {
409383
// coverage
410384
};
411385
}
386+
387+
static long getMillisFixedOrCalendar(String value) {
388+
DateHistogramInterval interval = new DateHistogramInterval(value);
389+
if (isCalendarInterval(interval)) {
390+
DateTimeUnit intervalUnit = DateHistogramAggregationBuilder.DATE_FIELD_UNITS.get(interval.toString());
391+
return intervalUnit.field(DateTimeZone.UTC).getDurationField().getUnitMillis();
392+
} else {
393+
return TimeValue.parseTimeValue(value, "date_histo.comparator.interval").getMillis();
394+
}
395+
}
412396
}

x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/RollupJobIdentifierUtilTests.java

+124
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
1616
import org.elasticsearch.search.aggregations.support.ValueType;
1717
import org.elasticsearch.test.ESTestCase;
18+
import org.elasticsearch.xpack.core.rollup.RollupField;
1819
import org.elasticsearch.xpack.core.rollup.action.RollupJobCaps;
1920
import org.elasticsearch.xpack.core.rollup.job.DateHistogramGroupConfig;
2021
import org.elasticsearch.xpack.core.rollup.job.GroupConfig;
@@ -24,17 +25,22 @@
2425
import org.elasticsearch.xpack.core.rollup.job.TermsGroupConfig;
2526
import org.joda.time.DateTimeZone;
2627

28+
import java.util.ArrayList;
2729
import java.util.Arrays;
2830
import java.util.HashSet;
2931
import java.util.List;
32+
import java.util.Map;
3033
import java.util.Set;
3134

3235
import static java.util.Collections.emptyList;
3336
import static java.util.Collections.singletonList;
3437
import static org.hamcrest.Matchers.equalTo;
38+
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
3539

3640
public class RollupJobIdentifierUtilTests extends ESTestCase {
3741

42+
private static final List<String> UNITS = new ArrayList<>(DateHistogramAggregationBuilder.DATE_FIELD_UNITS.keySet());
43+
3844
public void testOneMatch() {
3945
final GroupConfig group = new GroupConfig(new DateHistogramGroupConfig("foo", new DateHistogramInterval("1h")));
4046
final RollupJobConfig job = new RollupJobConfig("foo", "index", "rollup", "*/5 * * * * ?", 10, group, emptyList(), null);
@@ -577,6 +583,124 @@ public void testValidateCalendarInterval() {
577583
assertFalse(valid);
578584
}
579585

586+
public void testComparatorMixed() {
587+
int numCaps = randomIntBetween(1, 10);
588+
List<RollupJobCaps> caps = new ArrayList<>(numCaps);
589+
590+
for (int i = 0; i < numCaps; i++) {
591+
DateHistogramInterval interval = getRandomInterval();
592+
GroupConfig group = new GroupConfig(new DateHistogramGroupConfig("foo", interval));
593+
RollupJobConfig job = new RollupJobConfig("foo", "index", "rollup", "*/5 * * * * ?", 10, group, emptyList(), null);
594+
RollupJobCaps cap = new RollupJobCaps(job);
595+
caps.add(cap);
596+
}
597+
598+
caps.sort(RollupJobIdentifierUtils.COMPARATOR);
599+
600+
// This only tests for calendar/fixed ordering, ignoring the other criteria
601+
for (int i = 1; i < numCaps; i++) {
602+
RollupJobCaps a = caps.get(i - 1);
603+
RollupJobCaps b = caps.get(i);
604+
long aMillis = getMillis(a);
605+
long bMillis = getMillis(b);
606+
607+
assertThat(aMillis, greaterThanOrEqualTo(bMillis));
608+
609+
}
610+
}
611+
612+
public void testComparatorFixed() {
613+
int numCaps = randomIntBetween(1, 10);
614+
List<RollupJobCaps> caps = new ArrayList<>(numCaps);
615+
616+
for (int i = 0; i < numCaps; i++) {
617+
DateHistogramInterval interval = getRandomFixedInterval();
618+
GroupConfig group = new GroupConfig(new DateHistogramGroupConfig("foo", interval));
619+
RollupJobConfig job = new RollupJobConfig("foo", "index", "rollup", "*/5 * * * * ?", 10, group, emptyList(), null);
620+
RollupJobCaps cap = new RollupJobCaps(job);
621+
caps.add(cap);
622+
}
623+
624+
caps.sort(RollupJobIdentifierUtils.COMPARATOR);
625+
626+
// This only tests for fixed ordering, ignoring the other criteria
627+
for (int i = 1; i < numCaps; i++) {
628+
RollupJobCaps a = caps.get(i - 1);
629+
RollupJobCaps b = caps.get(i);
630+
long aMillis = getMillis(a);
631+
long bMillis = getMillis(b);
632+
633+
assertThat(aMillis, greaterThanOrEqualTo(bMillis));
634+
635+
}
636+
}
637+
638+
public void testComparatorCalendar() {
639+
int numCaps = randomIntBetween(1, 10);
640+
List<RollupJobCaps> caps = new ArrayList<>(numCaps);
641+
642+
for (int i = 0; i < numCaps; i++) {
643+
DateHistogramInterval interval = getRandomCalendarInterval();
644+
GroupConfig group = new GroupConfig(new DateHistogramGroupConfig("foo", interval));
645+
RollupJobConfig job = new RollupJobConfig("foo", "index", "rollup", "*/5 * * * * ?", 10, group, emptyList(), null);
646+
RollupJobCaps cap = new RollupJobCaps(job);
647+
caps.add(cap);
648+
}
649+
650+
caps.sort(RollupJobIdentifierUtils.COMPARATOR);
651+
652+
// This only tests for calendar ordering, ignoring the other criteria
653+
for (int i = 1; i < numCaps; i++) {
654+
RollupJobCaps a = caps.get(i - 1);
655+
RollupJobCaps b = caps.get(i);
656+
long aMillis = getMillis(a);
657+
long bMillis = getMillis(b);
658+
659+
assertThat(aMillis, greaterThanOrEqualTo(bMillis));
660+
661+
}
662+
}
663+
664+
private static long getMillis(RollupJobCaps cap) {
665+
for (RollupJobCaps.RollupFieldCaps fieldCaps : cap.getFieldCaps().values()) {
666+
for (Map<String, Object> agg : fieldCaps.getAggs()) {
667+
if (agg.get(RollupField.AGG).equals(DateHistogramAggregationBuilder.NAME)) {
668+
return RollupJobIdentifierUtils.getMillisFixedOrCalendar((String) agg.get(RollupField.INTERVAL));
669+
}
670+
}
671+
}
672+
return Long.MAX_VALUE;
673+
}
674+
675+
private static DateHistogramInterval getRandomInterval() {
676+
if (randomBoolean()) {
677+
return getRandomFixedInterval();
678+
}
679+
return getRandomCalendarInterval();
680+
}
681+
682+
private static DateHistogramInterval getRandomFixedInterval() {
683+
int value = randomIntBetween(1, 1000);
684+
String unit;
685+
int randomValue = randomInt(4);
686+
if (randomValue == 0) {
687+
unit = "ms";
688+
} else if (randomValue == 1) {
689+
unit = "s";
690+
} else if (randomValue == 2) {
691+
unit = "m";
692+
} else if (randomValue == 3) {
693+
unit = "h";
694+
} else {
695+
unit = "d";
696+
}
697+
return new DateHistogramInterval(Integer.toString(value) + unit);
698+
}
699+
700+
private static DateHistogramInterval getRandomCalendarInterval() {
701+
return new DateHistogramInterval(UNITS.get(randomIntBetween(0, UNITS.size()-1)));
702+
}
703+
580704
private Set<RollupJobCaps> singletonSet(RollupJobCaps cap) {
581705
Set<RollupJobCaps> caps = new HashSet<>();
582706
caps.add(cap);

0 commit comments

Comments
 (0)