Skip to content

Commit 76b47bc

Browse files
authored
Add time series params to unsigned_long and scaled_float (#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 #78100 Relates to #76766, #74450 and #74014
1 parent dba7b1c commit 76b47bc

File tree

9 files changed

+383
-28
lines changed

9 files changed

+383
-28
lines changed

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

+53-7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.elasticsearch.index.mapper.SimpleMappedFieldType;
3939
import org.elasticsearch.index.mapper.SourceValueFetcher;
4040
import org.elasticsearch.index.mapper.TextSearchInfo;
41+
import org.elasticsearch.index.mapper.TimeSeriesParams;
4142
import org.elasticsearch.index.mapper.ValueFetcher;
4243
import org.elasticsearch.index.query.SearchExecutionContext;
4344
import org.elasticsearch.search.DocValueFormat;
@@ -91,6 +92,13 @@ public static class Builder extends FieldMapper.Builder {
9192

9293
private final Parameter<Map<String, String>> meta = Parameter.metaParam();
9394

95+
/**
96+
* Parameter that marks this field as a time series metric defining its time series metric type.
97+
* For the numeric fields gauge and counter metric types are
98+
* supported
99+
*/
100+
private final Parameter<TimeSeriesParams.MetricType> metric;
101+
94102
public Builder(String name, Settings settings) {
95103
this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
96104
}
@@ -101,6 +109,18 @@ public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDe
101109
= Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
102110
this.coerce
103111
= Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);
112+
113+
this.metric = TimeSeriesParams.metricParam(
114+
m -> toType(m).metricType,
115+
TimeSeriesParams.MetricType.gauge,
116+
TimeSeriesParams.MetricType.counter
117+
).addValidator(v -> {
118+
if (v != null && hasDocValues.getValue() == false) {
119+
throw new IllegalArgumentException(
120+
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
121+
);
122+
}
123+
});
104124
}
105125

106126
Builder scalingFactor(double scalingFactor) {
@@ -113,15 +133,28 @@ Builder nullValue(double nullValue) {
113133
return this;
114134
}
115135

136+
public Builder metric(TimeSeriesParams.MetricType metric) {
137+
this.metric.setValue(metric);
138+
return this;
139+
}
140+
116141
@Override
117142
protected List<Parameter<?>> getParameters() {
118-
return List.of(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue);
143+
return List.of(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue, metric);
119144
}
120145

121146
@Override
122147
public ScaledFloatFieldMapper build(MapperBuilderContext context) {
123-
ScaledFloatFieldType type = new ScaledFloatFieldType(context.buildFullName(name), indexed.getValue(), stored.getValue(),
124-
hasDocValues.getValue(), meta.getValue(), scalingFactor.getValue(), nullValue.getValue());
148+
ScaledFloatFieldType type = new ScaledFloatFieldType(
149+
context.buildFullName(name),
150+
indexed.getValue(),
151+
stored.getValue(),
152+
hasDocValues.getValue(),
153+
meta.getValue(),
154+
scalingFactor.getValue(),
155+
nullValue.getValue(),
156+
metric.getValue()
157+
);
125158
return new ScaledFloatFieldMapper(name, type, multiFieldsBuilder.build(this, context), copyTo.build(), this);
126159
}
127160
}
@@ -132,16 +165,20 @@ public static final class ScaledFloatFieldType extends SimpleMappedFieldType {
132165

133166
private final double scalingFactor;
134167
private final Double nullValue;
168+
private final TimeSeriesParams.MetricType metricType;
169+
135170

136171
public ScaledFloatFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues,
137-
Map<String, String> meta, double scalingFactor, Double nullValue) {
172+
Map<String, String> meta, double scalingFactor, Double nullValue,
173+
TimeSeriesParams.MetricType metricType) {
138174
super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_WITHOUT_TERMS, meta);
139175
this.scalingFactor = scalingFactor;
140176
this.nullValue = nullValue;
177+
this.metricType = metricType;
141178
}
142179

143180
public ScaledFloatFieldType(String name, double scalingFactor) {
144-
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null);
181+
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null, null);
145182
}
146183

147184
public double getScalingFactor() {
@@ -260,6 +297,14 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
260297
private double scale(Object input) {
261298
return new BigDecimal(Double.toString(parse(input))).multiply(BigDecimal.valueOf(scalingFactor)).doubleValue();
262299
}
300+
301+
/**
302+
* If field is a time series metric field, returns its metric type
303+
* @return the metric type or null
304+
*/
305+
public TimeSeriesParams.MetricType getMetricType() {
306+
return metricType;
307+
}
263308
}
264309

265310
private final Explicit<Boolean> ignoreMalformed;
@@ -272,6 +317,7 @@ private double scale(Object input) {
272317

273318
private final boolean ignoreMalformedByDefault;
274319
private final boolean coerceByDefault;
320+
private final TimeSeriesParams.MetricType metricType;
275321

276322
private ScaledFloatFieldMapper(
277323
String simpleName,
@@ -289,6 +335,7 @@ private ScaledFloatFieldMapper(
289335
this.coerce = builder.coerce.getValue();
290336
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
291337
this.coerceByDefault = builder.coerce.getDefaultValue().value();
338+
this.metricType = builder.metric.getValue();
292339
}
293340

294341
boolean coerce() {
@@ -311,12 +358,11 @@ protected String contentType() {
311358

312359
@Override
313360
public FieldMapper.Builder getMergeBuilder() {
314-
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).init(this);
361+
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).metric(metricType).init(this);
315362
}
316363

317364
@Override
318365
protected void parseCreateField(DocumentParserContext context) throws IOException {
319-
320366
XContentParser parser = context.parser();
321367
Object value;
322368
Number numericValue = null;

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

+41-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import org.elasticsearch.index.mapper.MapperTestCase;
2323
import org.elasticsearch.index.mapper.ParsedDocument;
2424
import org.elasticsearch.index.mapper.SourceToParse;
25-
import org.elasticsearch.index.mapper.extras.MapperExtrasPlugin;
26-
import org.elasticsearch.index.mapper.extras.ScaledFloatFieldMapper;
2725
import org.elasticsearch.plugins.Plugin;
2826

2927
import java.io.IOException;
@@ -280,6 +278,47 @@ public void testRejectIndexOptions() {
280278
containsString("Failed to parse mapping: unknown parameter [index_options] on mapper [field] of type [scaled_float]"));
281279
}
282280

281+
public void testMetricType() throws IOException {
282+
// Test default setting
283+
MapperService mapperService = createMapperService(fieldMapping(b -> minimalMapping(b)));
284+
ScaledFloatFieldMapper.ScaledFloatFieldType ft = (ScaledFloatFieldMapper.ScaledFloatFieldType) mapperService.fieldType("field");
285+
assertNull(ft.getMetricType());
286+
287+
assertMetricType("gauge", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
288+
assertMetricType("counter", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
289+
290+
{
291+
// Test invalid metric type for this field type
292+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
293+
minimalMapping(b);
294+
b.field("time_series_metric", "histogram");
295+
})));
296+
assertThat(
297+
e.getCause().getMessage(),
298+
containsString("Unknown value [histogram] for field [time_series_metric] - accepted values are [gauge, counter]")
299+
);
300+
}
301+
{
302+
// Test invalid metric type for this field type
303+
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
304+
minimalMapping(b);
305+
b.field("time_series_metric", "unknown");
306+
})));
307+
assertThat(
308+
e.getCause().getMessage(),
309+
containsString("Unknown value [unknown] for field [time_series_metric] - accepted values are [gauge, counter]")
310+
);
311+
}
312+
}
313+
314+
public void testMetricAndDocvalues() {
315+
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
316+
minimalMapping(b);
317+
b.field("time_series_metric", "counter").field("doc_values", false);
318+
})));
319+
assertThat(e.getCause().getMessage(), containsString("Field [time_series_metric] requires that [doc_values] is true"));
320+
}
321+
283322
@Override
284323
protected void randomFetchTestFieldConfig(XContentBuilder b) throws IOException {
285324
// 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/extras/ScaledFloatFieldTypeTests.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ public void testRangeQuery() throws IOException {
6060
// this test checks that searching scaled floats yields the same results as
6161
// searching doubles that are rounded to the closest half float
6262
ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(
63-
"scaled_float", true, false, false, Collections.emptyMap(), 0.1 + randomDouble() * 100, null);
63+
"scaled_float",
64+
true,
65+
false,
66+
false,
67+
Collections.emptyMap(),
68+
0.1 + randomDouble() * 100,
69+
null,
70+
null
71+
);
6472
Directory dir = newDirectory();
6573
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(null));
6674
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

+9-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);
@@ -1153,7 +1152,7 @@ public MetricType getMetricType() {
11531152
private final boolean dimension;
11541153
private final ScriptCompiler scriptCompiler;
11551154
private final Script script;
1156-
private final TimeSeriesParams.MetricType metricType;
1155+
private final MetricType metricType;
11571156

11581157
private NumberFieldMapper(
11591158
String simpleName,
@@ -1262,8 +1261,7 @@ protected void indexScriptValues(SearchLookup searchLookup, LeafReaderContext re
12621261

12631262
@Override
12641263
public FieldMapper.Builder getMergeBuilder() {
1265-
return new Builder(simpleName(), type, scriptCompiler, ignoreMalformedByDefault, coerceByDefault)
1266-
.dimension(dimension)
1264+
return new Builder(simpleName(), type, scriptCompiler, ignoreMalformedByDefault, coerceByDefault).dimension(dimension)
12671265
.metric(metricType)
12681266
.init(this);
12691267
}

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)