Skip to content

Commit

Permalink
Add xpack info and usage endpoints for runtime fields (#66027)
Browse files Browse the repository at this point in the history
Relates to #59332
  • Loading branch information
javanna committed Dec 8, 2020
1 parent 573430f commit e7e1eda
Show file tree
Hide file tree
Showing 10 changed files with 713 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/reference/rest-api/info.asciidoc
Expand Up @@ -115,6 +115,10 @@ Example response:
"available": true,
"enabled": true
},
"runtime_fields": {
"available": true,
"enabled": true
},
"searchable_snapshots" : {
"available" : true,
"enabled" : true
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/rest-api/usage.asciidoc
Expand Up @@ -351,6 +351,11 @@ GET /_xpack/usage
"aggregate_metric" : {
"available" : true,
"enabled" : true
},
"runtime_fields" : {
"available" : true,
"enabled" : true,
"field_types" : []
}
}
------------------------------------------------------------
Expand Down
Expand Up @@ -203,6 +203,7 @@
import org.elasticsearch.xpack.core.rollup.action.StopRollupJobAction;
import org.elasticsearch.xpack.core.rollup.job.RollupJob;
import org.elasticsearch.xpack.core.rollup.job.RollupJobStatus;
import org.elasticsearch.xpack.core.runtimefields.RuntimeFieldsFeatureSetUsage;
import org.elasticsearch.xpack.core.search.action.ClosePointInTimeAction;
import org.elasticsearch.xpack.core.search.action.GetAsyncSearchAction;
import org.elasticsearch.xpack.core.search.action.GetAsyncStatusAction;
Expand Down Expand Up @@ -678,7 +679,8 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
// Data Streams
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.DATA_STREAMS, DataStreamFeatureSetUsage::new),
// Data Tiers
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.DATA_TIERS, DataTiersFeatureSetUsage::new)
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.DATA_TIERS, DataTiersFeatureSetUsage::new),
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.RUNTIME_FIELDS, RuntimeFieldsFeatureSetUsage::new)
).stream(),
MlEvaluationNamedXContentProvider.getNamedWriteables().stream()
).collect(toList());
Expand Down
Expand Up @@ -55,20 +55,37 @@ public final class XPackField {
public static final String ANALYTICS = "analytics";
/** Name constant for the enrich plugin. */
public static final String ENRICH = "enrich";
/** Name constant for the constant-keyword plugin. */
/**
* Name constant for the constant-keyword plugin.
*/
public static final String CONSTANT_KEYWORD = "constant_keyword";
/** Name constant for the searchable snapshots feature. */
/**
* Name constant for the searchable snapshots feature.
*/
public static final String SEARCHABLE_SNAPSHOTS = "searchable_snapshots";
/** Name constant for the data streams feature. */
/**
* Name constant for the data streams feature.
*/
public static final String DATA_STREAMS = "data_streams";
/** Name constant for the data tiers feature. */
/**
* Name constant for the data tiers feature.
*/
public static final String DATA_TIERS = "data_tiers";
/** Name constant for the aggregate_metric plugin. */
/**
* Name constant for the aggregate_metric plugin.
*/
public static final String AGGREGATE_METRIC = "aggregate_metric";
/** Name constant for the operator privileges feature. */
/**
* Name constant for the runtime fields plugin.
*/
public static final String RUNTIME_FIELDS = "runtime_fields";
/**
* Name constant for the operator privileges feature.
*/
public static final String OPERATOR_PRIVILEGES = "operator_privileges";

private XPackField() {}
private XPackField() {
}

public static String featureSettingPrefix(String featureName) {
return XPackField.SETTINGS_NAME + "." + featureName;
Expand Down
@@ -0,0 +1,274 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.runtimefields;

import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import org.elasticsearch.xpack.core.XPackField;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RuntimeFieldsFeatureSetUsage extends XPackFeatureSet.Usage {

public static RuntimeFieldsFeatureSetUsage fromMetadata(Iterable<IndexMetadata> metadata) {
Map<String, RuntimeFieldStats> fieldTypes = new HashMap<>();
for (IndexMetadata indexMetadata : metadata) {
if (indexMetadata.isSystem()) {
// Don't include system indices in statistics about mappings, we care about the user's indices.
continue;
}
Set<String> indexFieldTypes = new HashSet<>();
MappingMetadata mappingMetadata = indexMetadata.mapping();
if (mappingMetadata != null) {
Object runtimeObject = mappingMetadata.getSourceAsMap().get("runtime");
if (runtimeObject instanceof Map == false) {
continue;
}
Map<?, ?> runtimeMappings = (Map<?, ?>) runtimeObject;
for (Object runtimeFieldMappingObject : runtimeMappings.values()) {
if (runtimeFieldMappingObject instanceof Map == false) {
continue;
}
Map<?, ?> runtimeFieldMapping = (Map<?, ?>) runtimeFieldMappingObject;
Object typeObject = runtimeFieldMapping.get("type");
if (typeObject == null) {
continue;
}
String type = typeObject.toString();
RuntimeFieldStats stats = fieldTypes.computeIfAbsent(type, RuntimeFieldStats::new);
stats.count++;
if (indexFieldTypes.add(type)) {
stats.indexCount++;
}
Object scriptObject = runtimeFieldMapping.get("script");
if (scriptObject == null) {
stats.scriptLessCount++;
} else if (scriptObject instanceof Map) {
Map<?, ?> script = (Map<?, ?>) scriptObject;
Object sourceObject = script.get("source");
if (sourceObject != null) {
String scriptSource = sourceObject.toString();
int chars = scriptSource.length();
long lines = scriptSource.split("\\n").length;
int docUsages = countOccurrences(scriptSource, "doc[\\[\\.]");
int sourceUsages = countOccurrences(scriptSource, "params\\._source");
stats.update(chars, lines, sourceUsages, docUsages);
}
Object langObject = script.get("lang");
if (langObject != null) {
stats.scriptLangs.add(langObject.toString());
}
}
}
}
}
List<RuntimeFieldStats> runtimeFieldStats = new ArrayList<>(fieldTypes.values());
runtimeFieldStats.sort(Comparator.comparing(RuntimeFieldStats::type));
return new RuntimeFieldsFeatureSetUsage(Collections.unmodifiableList(runtimeFieldStats));
}

private final List<RuntimeFieldStats> stats;

RuntimeFieldsFeatureSetUsage(List<RuntimeFieldStats> stats) {
super(XPackField.RUNTIME_FIELDS, true, true);
this.stats = stats;
}

public RuntimeFieldsFeatureSetUsage(StreamInput in) throws IOException {
super(in);
this.stats = in.readList(RuntimeFieldStats::new);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(stats);
}

List<RuntimeFieldStats> getRuntimeFieldStats() {
return stats;
}

@Override
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
super.innerXContent(builder, params);
builder.startArray("field_types");
for (RuntimeFieldStats stats : stats) {
stats.toXContent(builder, params);
}
builder.endArray();
}

@Override
public Version getMinimalSupportedVersion() {
return Version.V_7_11_0;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RuntimeFieldsFeatureSetUsage that = (RuntimeFieldsFeatureSetUsage) o;
return stats.equals(that.stats);
}

@Override
public int hashCode() {
return Objects.hash(stats);
}

private static int countOccurrences(String script, String keyword) {
int occurrences = 0;
Pattern pattern = Pattern.compile(keyword);
Matcher matcher = pattern.matcher(script);
while (matcher.find()) {
occurrences++;
}
return occurrences;
}

static final class RuntimeFieldStats implements Writeable, ToXContentObject {
private final String type;
private int count = 0;
private int indexCount = 0;
private final Set<String> scriptLangs;
private long scriptLessCount = 0;
private long maxLines = 0;
private long totalLines = 0;
private long maxChars = 0;
private long totalChars = 0;
private long maxSourceUsages = 0;
private long totalSourceUsages = 0;
private long maxDocUsages = 0;
private long totalDocUsages = 0;

RuntimeFieldStats(String type) {
this.type = Objects.requireNonNull(type);
this.scriptLangs = new HashSet<>();
}

RuntimeFieldStats(StreamInput in) throws IOException {
this.type = in.readString();
this.count = in.readInt();
this.indexCount = in.readInt();
this.scriptLangs = in.readSet(StreamInput::readString);
this.scriptLessCount = in.readLong();
this.maxLines = in.readLong();
this.totalLines = in.readLong();
this.maxChars = in.readLong();
this.totalChars = in.readLong();
this.maxSourceUsages = in.readLong();
this.totalSourceUsages = in.readLong();
this.maxDocUsages = in.readLong();
this.totalDocUsages = in.readLong();
}

String type() {
return type;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeInt(count);
out.writeInt(indexCount);
out.writeCollection(scriptLangs, StreamOutput::writeString);
out.writeLong(scriptLessCount);
out.writeLong(maxLines);
out.writeLong(totalLines);
out.writeLong(maxChars);
out.writeLong(totalChars);
out.writeLong(maxSourceUsages);
out.writeLong(totalSourceUsages);
out.writeLong(maxDocUsages);
out.writeLong(totalDocUsages);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("name", type);
builder.field("count", count);
builder.field("index_count", indexCount);
builder.field("scriptless_count", scriptLessCount);
builder.array("lang", scriptLangs.toArray(new String[0]));
builder.field("lines_max", maxLines);
builder.field("lines_total", totalLines);
builder.field("chars_max", maxChars);
builder.field("chars_total", totalChars);
builder.field("source_max", maxSourceUsages);
builder.field("source_total", totalSourceUsages);
builder.field("doc_max", maxDocUsages);
builder.field("doc_total", totalDocUsages);
builder.endObject();
return builder;
}

void update(int chars, long lines, int sourceUsages, int docUsages) {
this.maxChars = Math.max(this.maxChars, chars);
this.totalChars += chars;
this.maxLines = Math.max(this.maxLines, lines);
this.totalLines += lines;
this.totalSourceUsages += sourceUsages;
this.maxSourceUsages = Math.max(this.maxSourceUsages, sourceUsages);
this.totalDocUsages += docUsages;
this.maxDocUsages = Math.max(this.maxDocUsages, docUsages);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RuntimeFieldStats that = (RuntimeFieldStats) o;
return count == that.count &&
indexCount == that.indexCount &&
scriptLessCount == that.scriptLessCount &&
maxLines == that.maxLines &&
totalLines == that.totalLines &&
maxChars == that.maxChars &&
totalChars == that.totalChars &&
maxSourceUsages == that.maxSourceUsages &&
totalSourceUsages == that.totalSourceUsages &&
maxDocUsages == that.maxDocUsages &&
totalDocUsages == that.totalDocUsages &&
type.equals(that.type) &&
scriptLangs.equals(that.scriptLangs);
}

@Override
public int hashCode() {
return Objects.hash(type, count, indexCount, scriptLangs, scriptLessCount, maxLines, totalLines, maxChars, totalChars,
maxSourceUsages, totalSourceUsages, maxDocUsages, totalDocUsages);
}
}
}

0 comments on commit e7e1eda

Please sign in to comment.