Skip to content

Commit b1474c0

Browse files
committed
Add time series params to unsigned_long and scaled_float (elastic#78204)
Added the time_series_metric mapping parameter to the unsigned_long and scaled_float field types Added the time_series_dimension mapping parameter to the unsigned_long field type Fixes elastic#78100 Relates to elastic#76766, elastic#74450 and elastic#74014
1 parent e681a7c commit b1474c0

File tree

9 files changed

+383
-26
lines changed

9 files changed

+383
-26
lines changed

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapper.java

+52-7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ public static class Builder extends FieldMapper.Builder {
8585

8686
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
8787

88+
/**
89+
* Parameter that marks this field as a time series metric defining its time series metric type.
90+
* For the numeric fields gauge and counter metric types are
91+
* supported
92+
*/
93+
private final Parameter<TimeSeriesParams.MetricType> metric;
94+
8895
public Builder(String name, Settings settings) {
8996
this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
9097
}
@@ -95,6 +102,18 @@ public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDe
95102
= Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
96103
this.coerce
97104
= Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
105+
106+
this.metric = TimeSeriesParams.metricParam(
107+
m -> toType(m).metricType,
108+
TimeSeriesParams.MetricType.gauge,
109+
TimeSeriesParams.MetricType.counter
110+
).addValidator(v -> {
111+
if (v != null && hasDocValues.getValue() == false) {
112+
throw new IllegalArgumentException(
113+
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
114+
);
115+
}
116+
});
98117
}
99118

100119
Builder scalingFactor(double scalingFactor) {
@@ -107,15 +126,28 @@ Builder nullValue(double nullValue) {
107126
return this;
108127
}
109128

129+
public Builder metric(TimeSeriesParams.MetricType metric) {
130+
this.metric.setValue(metric);
131+
return this;
132+
}
133+
110134
@Override
111135
protected List<Parameter<?>> getParameters() {
112-
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue);
136+
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue, metric);
113137
}
114138

115139
@Override
116140
public ScaledFloatFieldMapper build(MapperBuilderContext context) {
117-
ScaledFloatFieldType type = new ScaledFloatFieldType(context.buildFullName(name), indexed.getValue(), stored.getValue(),
118-
hasDocValues.getValue(), meta.getValue(), scalingFactor.getValue(), nullValue.getValue());
141+
ScaledFloatFieldType type = new ScaledFloatFieldType(
142+
context.buildFullName(name),
143+
indexed.getValue(),
144+
stored.getValue(),
145+
hasDocValues.getValue(),
146+
meta.getValue(),
147+
scalingFactor.getValue(),
148+
nullValue.getValue(),
149+
metric.getValue()
150+
);
119151
return new ScaledFloatFieldMapper(name, type, multiFieldsBuilder.build(this, context), copyTo.build(), this);
120152
}
121153
}
@@ -126,16 +158,20 @@ public static final class ScaledFloatFieldType extends SimpleMappedFieldType {
126158

127159
private final double scalingFactor;
128160
private final Double nullValue;
161+
private final TimeSeriesParams.MetricType metricType;
162+
129163

130164
public ScaledFloatFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues,
131-
Map<String, String> meta, double scalingFactor, Double nullValue) {
165+
Map<String, String> meta, double scalingFactor, Double nullValue,
166+
TimeSeriesParams.MetricType metricType) {
132167
super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_WITHOUT_TERMS, meta);
133168
this.scalingFactor = scalingFactor;
134169
this.nullValue = nullValue;
170+
this.metricType = metricType;
135171
}
136172

137173
public ScaledFloatFieldType(String name, double scalingFactor) {
138-
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null);
174+
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null, null);
139175
}
140176

141177
public double getScalingFactor() {
@@ -266,6 +302,14 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
266302
private double scale(Object input) {
267303
return new BigDecimal(Double.toString(parse(input))).multiply(BigDecimal.valueOf(scalingFactor)).doubleValue();
268304
}
305+
306+
/**
307+
* If field is a time series metric field, returns its metric type
308+
* @return the metric type or null
309+
*/
310+
public TimeSeriesParams.MetricType getMetricType() {
311+
return metricType;
312+
}
269313
}
270314

271315
private final Explicit<Boolean> ignoreMalformed;
@@ -278,6 +322,7 @@ private double scale(Object input) {
278322

279323
private final boolean ignoreMalformedByDefault;
280324
private final boolean coerceByDefault;
325+
private final TimeSeriesParams.MetricType metricType;
281326

282327
private ScaledFloatFieldMapper(
283328
String simpleName,
@@ -295,6 +340,7 @@ private ScaledFloatFieldMapper(
295340
this.coerce = builder.coerce.getValue();
296341
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
297342
this.coerceByDefault = builder.coerce.getDefaultValue().value();
343+
this.metricType = builder.metric.getValue();
298344
}
299345

300346
boolean coerce() {
@@ -317,12 +363,11 @@ protected String contentType() {
317363

318364
@Override
319365
public FieldMapper.Builder getMergeBuilder() {
320-
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).init(this);
366+
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).metric(metricType).init(this);
321367
}
322368

323369
@Override
324370
protected void parseCreateField(DocumentParserContext context) throws IOException {
325-
326371
XContentParser parser = context.parser();
327372
Object value;
328373
Number numericValue = null;

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldMapperTests.java

+41
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,47 @@ public void testRejectIndexOptions() {
272272
assertWarnings("Parameter [index_options] has no effect on type [scaled_float] and will be removed in future");
273273
}
274274

275+
public void testMetricType() throws IOException {
276+
// Test default setting
277+
MapperService mapperService = createMapperService(fieldMapping(b -> minimalMapping(b)));
278+
ScaledFloatFieldMapper.ScaledFloatFieldType ft = (ScaledFloatFieldMapper.ScaledFloatFieldType) mapperService.fieldType("field");
279+
assertNull(ft.getMetricType());
280+
281+
assertMetricType("gauge", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
282+
assertMetricType("counter", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
283+
284+
{
285+
// Test invalid metric type for this field type
286+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
287+
minimalMapping(b);
288+
b.field("time_series_metric", "histogram");
289+
})));
290+
assertThat(
291+
e.getCause().getMessage(),
292+
containsString("Unknown value [histogram] for field [time_series_metric] - accepted values are [gauge, counter]")
293+
);
294+
}
295+
{
296+
// Test invalid metric type for this field type
297+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
298+
minimalMapping(b);
299+
b.field("time_series_metric", "unknown");
300+
})));
301+
assertThat(
302+
e.getCause().getMessage(),
303+
containsString("Unknown value [unknown] for field [time_series_metric] - accepted values are [gauge, counter]")
304+
);
305+
}
306+
}
307+
308+
public void testMetricAndDocvalues() {
309+
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
310+
minimalMapping(b);
311+
b.field("time_series_metric", "counter").field("doc_values", false);
312+
})));
313+
assertThat(e.getCause().getMessage(), containsString("Field [time_series_metric] requires that [doc_values] is true"));
314+
}
315+
275316
@Override
276317
protected void randomFetchTestFieldConfig(XContentBuilder b) throws IOException {
277318
// Large floats are a terrible idea but the round trip should still work no matter how badly you configure the field

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/ScaledFloatFieldTypeTests.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ public void testRangeQuery() throws IOException {
5454
// this test checks that searching scaled floats yields the same results as
5555
// searching doubles that are rounded to the closest half float
5656
ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(
57-
"scaled_float", true, false, false, Collections.emptyMap(), 0.1 + randomDouble() * 100, null);
57+
"scaled_float",
58+
true,
59+
false,
60+
false,
61+
Collections.emptyMap(),
62+
0.1 + randomDouble() * 100,
63+
null,
64+
null
65+
);
5866
Directory dir = newDirectory();
5967
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(null));
6068
final int numDocs = 1000;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
add time series mappings:
2+
- skip:
3+
version: " - 7.99.99"
4+
reason: introduced in 8.0.0 to be backported to 7.16.0
5+
6+
- do:
7+
indices.create:
8+
index: test_index
9+
body:
10+
settings:
11+
index:
12+
mode: time_series
13+
number_of_replicas: 0
14+
number_of_shards: 2
15+
mappings:
16+
properties:
17+
"@timestamp":
18+
type: date
19+
metricset:
20+
type: keyword
21+
time_series_dimension: true
22+
k8s:
23+
properties:
24+
pod:
25+
properties:
26+
availability_zone:
27+
type: short
28+
time_series_dimension: true
29+
uid:
30+
type: keyword
31+
time_series_dimension: true
32+
name:
33+
type: keyword
34+
ip:
35+
type: ip
36+
time_series_dimension: true
37+
network:
38+
properties:
39+
tx:
40+
type: long
41+
time_series_metric: counter
42+
rx:
43+
type: integer
44+
time_series_metric: gauge
45+
packets_dropped:
46+
type: long
47+
time_series_metric: gauge
48+
latency:
49+
type: double
50+
time_series_metric: gauge

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

+10-11
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,13 @@ public Builder(String name, NumberType type, ScriptCompiler compiler, boolean ig
144144
}
145145
});
146146

147-
this.metric = TimeSeriesParams.metricParam(m -> toType(m).metricType, MetricType.gauge, MetricType.counter)
148-
.addValidator(v -> {
149-
if (v != null && hasDocValues.getValue() == false) {
150-
throw new IllegalArgumentException(
151-
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
152-
);
153-
}
154-
});
147+
this.metric = TimeSeriesParams.metricParam(m -> toType(m).metricType, MetricType.gauge, MetricType.counter).addValidator(v -> {
148+
if (v != null && hasDocValues.getValue() == false) {
149+
throw new IllegalArgumentException(
150+
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
151+
);
152+
}
153+
}).precludesParameters(dimension);
155154

156155
this.script.precludesParameters(ignoreMalformed, coerce, nullValue);
157156
addScriptValidation(script, indexed, hasDocValues);
@@ -1161,10 +1160,10 @@ public MetricType getMetricType() {
11611160
private final FieldValues<Number> scriptValues;
11621161
private final boolean ignoreMalformedByDefault;
11631162
private final boolean coerceByDefault;
1163+
private final boolean dimension;
11641164
private final ScriptCompiler scriptCompiler;
11651165
private final Script script;
1166-
private final boolean dimension;
1167-
private final TimeSeriesParams.MetricType metricType;
1166+
private final MetricType metricType;
11681167

11691168
private NumberFieldMapper(
11701169
String simpleName,
@@ -1183,9 +1182,9 @@ private NumberFieldMapper(
11831182
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
11841183
this.coerceByDefault = builder.coerce.getDefaultValue().value();
11851184
this.scriptValues = builder.scriptValues();
1185+
this.dimension = builder.dimension.getValue();
11861186
this.scriptCompiler = builder.scriptCompiler;
11871187
this.script = builder.script.getValue();
1188-
this.dimension = builder.dimension.getValue();
11891188
this.metricType = builder.metric.getValue();
11901189
}
11911190

server/src/test/java/org/elasticsearch/index/mapper/WholeNumberFieldMapperTests.java

+11
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ public void testDimensionMultiValuedField() throws IOException {
8080
containsString("Dimension field [field] cannot be a multi-valued field"));
8181
}
8282

83+
public void testMetricAndDimension() {
84+
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
85+
minimalMapping(b);
86+
b.field("time_series_metric", "counter").field("time_series_dimension", true);
87+
})));
88+
assertThat(
89+
e.getCause().getMessage(),
90+
containsString("Field [time_series_dimension] cannot be set in conjunction with field [time_series_metric]")
91+
);
92+
}
93+
8394
@Override
8495
protected void registerParameters(ParameterChecker checker) throws IOException {
8596
super.registerParameters(checker);

0 commit comments

Comments
 (0)