Skip to content

Commit

Permalink
Add time series params to unsigned_long and scaled_float (elastic…
Browse files Browse the repository at this point in the history
…#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
  • Loading branch information
csoulios committed Sep 23, 2021
1 parent e681a7c commit b1474c0
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ public static class Builder extends FieldMapper.Builder {

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

/**
* Parameter that marks this field as a time series metric defining its time series metric type.
* For the numeric fields gauge and counter metric types are
* supported
*/
private final Parameter<TimeSeriesParams.MetricType> metric;

public Builder(String name, Settings settings) {
this(name, IGNORE_MALFORMED_SETTING.get(settings), COERCE_SETTING.get(settings));
}
Expand All @@ -95,6 +102,18 @@ public Builder(String name, boolean ignoreMalformedByDefault, boolean coerceByDe
= Parameter.explicitBoolParam("ignore_malformed", true, m -> toType(m).ignoreMalformed, ignoreMalformedByDefault);
this.coerce
= Parameter.explicitBoolParam("coerce", true, m -> toType(m).coerce, coerceByDefault);

this.metric = TimeSeriesParams.metricParam(
m -> toType(m).metricType,
TimeSeriesParams.MetricType.gauge,
TimeSeriesParams.MetricType.counter
).addValidator(v -> {
if (v != null && hasDocValues.getValue() == false) {
throw new IllegalArgumentException(
"Field [" + TimeSeriesParams.TIME_SERIES_METRIC_PARAM + "] requires that [" + hasDocValues.name + "] is true"
);
}
});
}

Builder scalingFactor(double scalingFactor) {
Expand All @@ -107,15 +126,28 @@ Builder nullValue(double nullValue) {
return this;
}

public Builder metric(TimeSeriesParams.MetricType metric) {
this.metric.setValue(metric);
return this;
}

@Override
protected List<Parameter<?>> getParameters() {
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue);
return Arrays.asList(indexed, hasDocValues, stored, ignoreMalformed, meta, scalingFactor, coerce, nullValue, metric);
}

@Override
public ScaledFloatFieldMapper build(MapperBuilderContext context) {
ScaledFloatFieldType type = new ScaledFloatFieldType(context.buildFullName(name), indexed.getValue(), stored.getValue(),
hasDocValues.getValue(), meta.getValue(), scalingFactor.getValue(), nullValue.getValue());
ScaledFloatFieldType type = new ScaledFloatFieldType(
context.buildFullName(name),
indexed.getValue(),
stored.getValue(),
hasDocValues.getValue(),
meta.getValue(),
scalingFactor.getValue(),
nullValue.getValue(),
metric.getValue()
);
return new ScaledFloatFieldMapper(name, type, multiFieldsBuilder.build(this, context), copyTo.build(), this);
}
}
Expand All @@ -126,16 +158,20 @@ public static final class ScaledFloatFieldType extends SimpleMappedFieldType {

private final double scalingFactor;
private final Double nullValue;
private final TimeSeriesParams.MetricType metricType;


public ScaledFloatFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues,
Map<String, String> meta, double scalingFactor, Double nullValue) {
Map<String, String> meta, double scalingFactor, Double nullValue,
TimeSeriesParams.MetricType metricType) {
super(name, indexed, stored, hasDocValues, TextSearchInfo.SIMPLE_MATCH_WITHOUT_TERMS, meta);
this.scalingFactor = scalingFactor;
this.nullValue = nullValue;
this.metricType = metricType;
}

public ScaledFloatFieldType(String name, double scalingFactor) {
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null);
this(name, true, false, true, Collections.emptyMap(), scalingFactor, null, null);
}

public double getScalingFactor() {
Expand Down Expand Up @@ -266,6 +302,14 @@ public DocValueFormat docValueFormat(String format, ZoneId timeZone) {
private double scale(Object input) {
return new BigDecimal(Double.toString(parse(input))).multiply(BigDecimal.valueOf(scalingFactor)).doubleValue();
}

/**
* If field is a time series metric field, returns its metric type
* @return the metric type or null
*/
public TimeSeriesParams.MetricType getMetricType() {
return metricType;
}
}

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

private final boolean ignoreMalformedByDefault;
private final boolean coerceByDefault;
private final TimeSeriesParams.MetricType metricType;

private ScaledFloatFieldMapper(
String simpleName,
Expand All @@ -295,6 +340,7 @@ private ScaledFloatFieldMapper(
this.coerce = builder.coerce.getValue();
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
this.coerceByDefault = builder.coerce.getDefaultValue().value();
this.metricType = builder.metric.getValue();
}

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

@Override
public FieldMapper.Builder getMergeBuilder() {
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).init(this);
return new Builder(simpleName(), ignoreMalformedByDefault, coerceByDefault).metric(metricType).init(this);
}

@Override
protected void parseCreateField(DocumentParserContext context) throws IOException {

XContentParser parser = context.parser();
Object value;
Number numericValue = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,47 @@ public void testRejectIndexOptions() {
assertWarnings("Parameter [index_options] has no effect on type [scaled_float] and will be removed in future");
}

public void testMetricType() throws IOException {
// Test default setting
MapperService mapperService = createMapperService(fieldMapping(b -> minimalMapping(b)));
ScaledFloatFieldMapper.ScaledFloatFieldType ft = (ScaledFloatFieldMapper.ScaledFloatFieldType) mapperService.fieldType("field");
assertNull(ft.getMetricType());

assertMetricType("gauge", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);
assertMetricType("counter", ScaledFloatFieldMapper.ScaledFloatFieldType::getMetricType);

{
// Test invalid metric type for this field type
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_metric", "histogram");
})));
assertThat(
e.getCause().getMessage(),
containsString("Unknown value [histogram] for field [time_series_metric] - accepted values are [gauge, counter]")
);
}
{
// Test invalid metric type for this field type
Exception e = expectThrows(MapperParsingException.class, () -> createMapperService(fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_metric", "unknown");
})));
assertThat(
e.getCause().getMessage(),
containsString("Unknown value [unknown] for field [time_series_metric] - accepted values are [gauge, counter]")
);
}
}

public void testMetricAndDocvalues() {
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_metric", "counter").field("doc_values", false);
})));
assertThat(e.getCause().getMessage(), containsString("Field [time_series_metric] requires that [doc_values] is true"));
}

@Override
protected void randomFetchTestFieldConfig(XContentBuilder b) throws IOException {
// Large floats are a terrible idea but the round trip should still work no matter how badly you configure the field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ public void testRangeQuery() throws IOException {
// this test checks that searching scaled floats yields the same results as
// searching doubles that are rounded to the closest half float
ScaledFloatFieldMapper.ScaledFloatFieldType ft = new ScaledFloatFieldMapper.ScaledFloatFieldType(
"scaled_float", true, false, false, Collections.emptyMap(), 0.1 + randomDouble() * 100, null);
"scaled_float",
true,
false,
false,
Collections.emptyMap(),
0.1 + randomDouble() * 100,
null,
null
);
Directory dir = newDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(null));
final int numDocs = 1000;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
add time series mappings:
- skip:
version: " - 7.99.99"
reason: introduced in 8.0.0 to be backported to 7.16.0

- do:
indices.create:
index: test_index
body:
settings:
index:
mode: time_series
number_of_replicas: 0
number_of_shards: 2
mappings:
properties:
"@timestamp":
type: date
metricset:
type: keyword
time_series_dimension: true
k8s:
properties:
pod:
properties:
availability_zone:
type: short
time_series_dimension: true
uid:
type: keyword
time_series_dimension: true
name:
type: keyword
ip:
type: ip
time_series_dimension: true
network:
properties:
tx:
type: long
time_series_metric: counter
rx:
type: integer
time_series_metric: gauge
packets_dropped:
type: long
time_series_metric: gauge
latency:
type: double
time_series_metric: gauge
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,13 @@ public Builder(String name, NumberType type, ScriptCompiler compiler, boolean ig
}
});

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

this.script.precludesParameters(ignoreMalformed, coerce, nullValue);
addScriptValidation(script, indexed, hasDocValues);
Expand Down Expand Up @@ -1161,10 +1160,10 @@ public MetricType getMetricType() {
private final FieldValues<Number> scriptValues;
private final boolean ignoreMalformedByDefault;
private final boolean coerceByDefault;
private final boolean dimension;
private final ScriptCompiler scriptCompiler;
private final Script script;
private final boolean dimension;
private final TimeSeriesParams.MetricType metricType;
private final MetricType metricType;

private NumberFieldMapper(
String simpleName,
Expand All @@ -1183,9 +1182,9 @@ private NumberFieldMapper(
this.ignoreMalformedByDefault = builder.ignoreMalformed.getDefaultValue().value();
this.coerceByDefault = builder.coerce.getDefaultValue().value();
this.scriptValues = builder.scriptValues();
this.dimension = builder.dimension.getValue();
this.scriptCompiler = builder.scriptCompiler;
this.script = builder.script.getValue();
this.dimension = builder.dimension.getValue();
this.metricType = builder.metric.getValue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ public void testDimensionMultiValuedField() throws IOException {
containsString("Dimension field [field] cannot be a multi-valued field"));
}

public void testMetricAndDimension() {
Exception e = expectThrows(MapperParsingException.class, () -> createDocumentMapper(fieldMapping(b -> {
minimalMapping(b);
b.field("time_series_metric", "counter").field("time_series_dimension", true);
})));
assertThat(
e.getCause().getMessage(),
containsString("Field [time_series_dimension] cannot be set in conjunction with field [time_series_metric]")
);
}

@Override
protected void registerParameters(ParameterChecker checker) throws IOException {
super.registerParameters(checker);
Expand Down
Loading

0 comments on commit b1474c0

Please sign in to comment.