diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsage.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsage.java index 00e42d46fb551..955989308a49e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsage.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsage.java @@ -7,18 +7,72 @@ package org.elasticsearch.xpack.core.vectors; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +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.Objects; public class VectorsFeatureSetUsage extends XPackFeatureSet.Usage { + private final int numDenseVectorFields; + private final int numSparseVectorFields; + private final int avgDenseVectorDims; + public VectorsFeatureSetUsage(StreamInput input) throws IOException { super(input); + numDenseVectorFields = input.readVInt(); + numSparseVectorFields = input.readVInt(); + avgDenseVectorDims = input.readVInt(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeVInt(numDenseVectorFields); + out.writeVInt(numSparseVectorFields); + out.writeVInt(avgDenseVectorDims); } - public VectorsFeatureSetUsage(boolean available, boolean enabled) { + public VectorsFeatureSetUsage(boolean available, boolean enabled, int numDenseVectorFields, int numSparseVectorFields, + int avgDenseVectorDims) { super(XPackField.VECTORS, available, enabled); + this.numDenseVectorFields = numDenseVectorFields; + this.numSparseVectorFields = numSparseVectorFields; + this.avgDenseVectorDims = avgDenseVectorDims; + } + + + @Override + protected void innerXContent(XContentBuilder builder, Params params) throws IOException { + super.innerXContent(builder, params); + builder.field("dense_vector_fields_count", numDenseVectorFields); + builder.field("sparse_vector_fields_count", numSparseVectorFields); + builder.field("dense_vector_dims_avg_count", avgDenseVectorDims); + } + + public int numDenseVectorFields() { + return numDenseVectorFields; + } + public int numSparseVectorFields() { + return numSparseVectorFields; + } + public int avgDenseVectorDims() { + return avgDenseVectorDims; + } + + @Override + public int hashCode() { + return Objects.hash(available, enabled, numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof VectorsFeatureSetUsage == false) return false; + VectorsFeatureSetUsage other = (VectorsFeatureSetUsage) obj; + return available == other.available && enabled == other.enabled && numDenseVectorFields == other.numDenseVectorFields + && numSparseVectorFields == other.numSparseVectorFields && avgDenseVectorDims == other.avgDenseVectorDims; } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsageTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsageTests.java new file mode 100644 index 0000000000000..f0874299f442e --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsageTests.java @@ -0,0 +1,50 @@ +/* + * 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.vectors; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.io.IOException; + +public class VectorsFeatureSetUsageTests extends AbstractWireSerializingTestCase { + + @Override + protected VectorsFeatureSetUsage createTestInstance() { + boolean available = randomBoolean(); + boolean enabled = randomBoolean(); + if (available && enabled) { + return new VectorsFeatureSetUsage(available, enabled, randomIntBetween(0, 100000), randomIntBetween(0, 100000), + randomIntBetween(0, 1024)); + } else { + return new VectorsFeatureSetUsage(available, enabled, 0, 0, 0); + } + } + + @Override + protected VectorsFeatureSetUsage mutateInstance(VectorsFeatureSetUsage instance) throws IOException { + boolean available = instance.available(); + boolean enabled = instance.enabled(); + int numDenseVectorFields = instance.numDenseVectorFields(); + int numSparseVectorFields = instance.numSparseVectorFields(); + int avgDenseVectorDims = instance.avgDenseVectorDims(); + + if (available == false || enabled == false) { + available = true; + enabled = true; + } + numDenseVectorFields = randomValueOtherThan(numDenseVectorFields, () -> randomIntBetween(0, 100000)); + numSparseVectorFields = randomValueOtherThan(numSparseVectorFields, () -> randomIntBetween(0, 100000)); + avgDenseVectorDims = randomValueOtherThan(avgDenseVectorDims, () -> randomIntBetween(0, 1024)); + return new VectorsFeatureSetUsage(available, enabled, numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims); + } + + @Override + protected Writeable.Reader instanceReader() { + return VectorsFeatureSetUsage::new; + } + +} diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialInfoTransportActionTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialInfoTransportActionTests.java index dbe0674eef746..b3236cf7265b9 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialInfoTransportActionTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialInfoTransportActionTests.java @@ -15,7 +15,6 @@ import org.elasticsearch.xpack.core.XPackFeatureSet; import org.elasticsearch.xpack.core.action.XPackUsageFeatureResponse; import org.elasticsearch.xpack.core.spatial.SpatialFeatureSetUsage; -import org.elasticsearch.xpack.core.vectors.VectorsFeatureSetUsage; import org.junit.Before; import static org.hamcrest.core.Is.is; @@ -67,7 +66,7 @@ public void testEnabled() throws Exception { BytesStreamOutput out = new BytesStreamOutput(); usage.writeTo(out); - XPackFeatureSet.Usage serializedUsage = new VectorsFeatureSetUsage(out.bytes().streamInput()); + XPackFeatureSet.Usage serializedUsage = new SpatialFeatureSetUsage(out.bytes().streamInput()); assertTrue(serializedUsage.enabled()); } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/vectors/50_vector_stats.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/vectors/50_vector_stats.yml new file mode 100644 index 0000000000000..abfc05670cff5 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/vectors/50_vector_stats.yml @@ -0,0 +1,46 @@ +setup: + - skip: + features: headers + version: " - 7.3.99" + reason: "vector stats was added from 7.4" + +--- +"Usage stats on vector fields": + - do: {xpack.usage: {}} + - match: { vectors.available: true } + - match: { vectors.enabled: true } + - match: { vectors.dense_vector_fields_count: 0 } + - match: { vectors.sparse_vector_fields_count: 0 } + - match: { vectors.dense_vector_dims_avg_count: 0 } + + - do: + indices.create: + index: test-index1 + body: + mappings: + properties: + my_dense_vector1: + type: dense_vector + dims: 10 + my_dense_vector2: + type: dense_vector + dims: 30 + + - do: + indices.create: + index: test-index2 + body: + mappings: + properties: + my_dense_vector3: + type: dense_vector + dims: 20 + my_sparse_vector1: + type: sparse_vector + + - do: {xpack.usage: {}} + - match: { vectors.available: true } + - match: { vectors.enabled: true } + - match: { vectors.dense_vector_fields_count: 3 } + - match: { vectors.sparse_vector_fields_count: 1 } + - match: { vectors.dense_vector_dims_avg_count: 20 } diff --git a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/VectorsUsageTransportAction.java b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/VectorsUsageTransportAction.java index 8c16d651f47ee..83bb1a37cd6bd 100644 --- a/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/VectorsUsageTransportAction.java +++ b/x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/VectorsUsageTransportAction.java @@ -8,7 +8,9 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -22,6 +24,10 @@ import org.elasticsearch.xpack.core.action.XPackUsageFeatureResponse; import org.elasticsearch.xpack.core.action.XPackUsageFeatureTransportAction; import org.elasticsearch.xpack.core.vectors.VectorsFeatureSetUsage; +import org.elasticsearch.xpack.vectors.mapper.DenseVectorFieldMapper; +import org.elasticsearch.xpack.vectors.mapper.SparseVectorFieldMapper; + +import java.util.Map; public class VectorsUsageTransportAction extends XPackUsageFeatureTransportAction { @@ -41,8 +47,42 @@ public VectorsUsageTransportAction(TransportService transportService, ClusterSer @Override protected void masterOperation(Task task, XPackUsageRequest request, ClusterState state, ActionListener listener) { + boolean vectorsAvailable = licenseState.isVectorsAllowed(); + boolean vectorsEnabled = XPackSettings.VECTORS_ENABLED.get(settings); + int numDenseVectorFields = 0; + int numSparseVectorFields = 0; + int avgDenseVectorDims = 0; + + if (vectorsAvailable && vectorsEnabled && state != null) { + for (IndexMetaData indexMetaData : state.metaData()) { + MappingMetaData mappingMetaData = indexMetaData.mapping(); + if (mappingMetaData != null) { + Map mappings = mappingMetaData.getSourceAsMap(); + if (mappings.containsKey("properties")) { + @SuppressWarnings("unchecked") Map> fieldMappings = + (Map>) mappings.get("properties"); + for (Map typeDefinition : fieldMappings.values()) { + String fieldType = (String) typeDefinition.get("type"); + if (fieldType != null) { + if (fieldType.equals(DenseVectorFieldMapper.CONTENT_TYPE)) { + numDenseVectorFields++; + int dims = (Integer) typeDefinition.get("dims"); + avgDenseVectorDims += dims; + } else if (fieldType.equals(SparseVectorFieldMapper.CONTENT_TYPE)) { + numSparseVectorFields++; + } + } + } + } + } + } + if (numDenseVectorFields > 0) { + avgDenseVectorDims = avgDenseVectorDims / numDenseVectorFields; + } + } VectorsFeatureSetUsage usage = - new VectorsFeatureSetUsage(licenseState.isVectorsAllowed(), XPackSettings.VECTORS_ENABLED.get(settings)); + new VectorsFeatureSetUsage(licenseState.isVectorsAllowed(), XPackSettings.VECTORS_ENABLED.get(settings), + numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims); listener.onResponse(new XPackUsageFeatureResponse(usage)); } }