Skip to content

Commit 0c68765

Browse files
Adds usage stats for vectors (#45023)
Example of usage: _xpack/usage "vectors": { "available": true, "enabled": true, "dense_vector_fields_count" : 1, "sparse_vector_fields_count" : 1, "dense_vector_dims_avg_count" : 100 } Backport for #44512
1 parent 5202d26 commit 0c68765

File tree

5 files changed

+242
-12
lines changed

5 files changed

+242
-12
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/vectors/VectorsFeatureSetUsage.java

+55-1
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,72 @@
77
package org.elasticsearch.xpack.core.vectors;
88

99
import org.elasticsearch.common.io.stream.StreamInput;
10+
import org.elasticsearch.common.io.stream.StreamOutput;
11+
import org.elasticsearch.common.xcontent.XContentBuilder;
1012
import org.elasticsearch.xpack.core.XPackFeatureSet;
1113
import org.elasticsearch.xpack.core.XPackField;
1214

1315
import java.io.IOException;
16+
import java.util.Objects;
1417

1518
public class VectorsFeatureSetUsage extends XPackFeatureSet.Usage {
1619

20+
private final int numDenseVectorFields;
21+
private final int numSparseVectorFields;
22+
private final int avgDenseVectorDims;
23+
1724
public VectorsFeatureSetUsage(StreamInput input) throws IOException {
1825
super(input);
26+
numDenseVectorFields = input.readVInt();
27+
numSparseVectorFields = input.readVInt();
28+
avgDenseVectorDims = input.readVInt();
29+
}
30+
31+
@Override
32+
public void writeTo(StreamOutput out) throws IOException {
33+
super.writeTo(out);
34+
out.writeVInt(numDenseVectorFields);
35+
out.writeVInt(numSparseVectorFields);
36+
out.writeVInt(avgDenseVectorDims);
1937
}
2038

21-
public VectorsFeatureSetUsage(boolean available, boolean enabled) {
39+
public VectorsFeatureSetUsage(boolean available, boolean enabled, int numDenseVectorFields, int numSparseVectorFields,
40+
int avgDenseVectorDims) {
2241
super(XPackField.VECTORS, available, enabled);
42+
this.numDenseVectorFields = numDenseVectorFields;
43+
this.numSparseVectorFields = numSparseVectorFields;
44+
this.avgDenseVectorDims = avgDenseVectorDims;
45+
}
46+
47+
48+
@Override
49+
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
50+
super.innerXContent(builder, params);
51+
builder.field("dense_vector_fields_count", numDenseVectorFields);
52+
builder.field("sparse_vector_fields_count", numSparseVectorFields);
53+
builder.field("dense_vector_dims_avg_count", avgDenseVectorDims);
54+
}
55+
56+
public int numDenseVectorFields() {
57+
return numDenseVectorFields;
58+
}
59+
public int numSparseVectorFields() {
60+
return numSparseVectorFields;
61+
}
62+
public int avgDenseVectorDims() {
63+
return avgDenseVectorDims;
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
return Objects.hash(available, enabled, numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims);
69+
}
70+
71+
@Override
72+
public boolean equals(Object obj) {
73+
if (obj instanceof VectorsFeatureSetUsage == false) return false;
74+
VectorsFeatureSetUsage other = (VectorsFeatureSetUsage) obj;
75+
return available == other.available && enabled == other.enabled && numDenseVectorFields == other.numDenseVectorFields
76+
&& numSparseVectorFields == other.numSparseVectorFields && avgDenseVectorDims == other.avgDenseVectorDims;
2377
}
2478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.core.vectors;
7+
8+
import org.elasticsearch.common.io.stream.Writeable;
9+
import org.elasticsearch.test.AbstractWireSerializingTestCase;
10+
11+
import java.io.IOException;
12+
13+
public class VectorsFeatureSetUsageTests extends AbstractWireSerializingTestCase<VectorsFeatureSetUsage> {
14+
15+
@Override
16+
protected VectorsFeatureSetUsage createTestInstance() {
17+
boolean available = randomBoolean();
18+
boolean enabled = randomBoolean();
19+
if (available && enabled) {
20+
return new VectorsFeatureSetUsage(available, enabled, randomIntBetween(0, 100000), randomIntBetween(0, 100000),
21+
randomIntBetween(0, 1024));
22+
} else {
23+
return new VectorsFeatureSetUsage(available, enabled, 0, 0, 0);
24+
}
25+
}
26+
27+
@Override
28+
protected VectorsFeatureSetUsage mutateInstance(VectorsFeatureSetUsage instance) throws IOException {
29+
boolean available = instance.available();
30+
boolean enabled = instance.enabled();
31+
int numDenseVectorFields = instance.numDenseVectorFields();
32+
int numSparseVectorFields = instance.numSparseVectorFields();
33+
int avgDenseVectorDims = instance.avgDenseVectorDims();
34+
35+
if (available == false || enabled == false) {
36+
available = true;
37+
enabled = true;
38+
}
39+
numDenseVectorFields = randomValueOtherThan(numDenseVectorFields, () -> randomIntBetween(0, 100000));
40+
numSparseVectorFields = randomValueOtherThan(numSparseVectorFields, () -> randomIntBetween(0, 100000));
41+
avgDenseVectorDims = randomValueOtherThan(avgDenseVectorDims, () -> randomIntBetween(0, 1024));
42+
return new VectorsFeatureSetUsage(available, enabled, numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims);
43+
}
44+
45+
@Override
46+
protected Writeable.Reader<VectorsFeatureSetUsage> instanceReader() {
47+
return VectorsFeatureSetUsage::new;
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
setup:
2+
- skip:
3+
features: headers
4+
version: " - 7.3.99"
5+
reason: "vector stats was added from 7.4"
6+
7+
---
8+
"Usage stats on vector fields":
9+
- do: {xpack.usage: {}}
10+
- match: { vectors.available: true }
11+
- match: { vectors.enabled: true }
12+
- match: { vectors.dense_vector_fields_count: 0 }
13+
- match: { vectors.sparse_vector_fields_count: 0 }
14+
- match: { vectors.dense_vector_dims_avg_count: 0 }
15+
16+
- do:
17+
indices.create:
18+
index: test-index1
19+
body:
20+
mappings:
21+
properties:
22+
my_dense_vector1:
23+
type: dense_vector
24+
dims: 10
25+
my_dense_vector2:
26+
type: dense_vector
27+
dims: 30
28+
29+
- do:
30+
indices.create:
31+
index: test-index2
32+
body:
33+
mappings:
34+
properties:
35+
my_dense_vector3:
36+
type: dense_vector
37+
dims: 20
38+
my_sparse_vector1:
39+
type: sparse_vector
40+
41+
- do: {xpack.usage: {}}
42+
- match: { vectors.available: true }
43+
- match: { vectors.enabled: true }
44+
- match: { vectors.dense_vector_fields_count: 3 }
45+
- match: { vectors.sparse_vector_fields_count: 1 }
46+
- match: { vectors.dense_vector_dims_avg_count: 20 }

x-pack/plugin/vectors/src/main/java/org/elasticsearch/xpack/vectors/VectorsFeatureSet.java

+43-2
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,32 @@
66
package org.elasticsearch.xpack.vectors;
77

88
import org.elasticsearch.action.ActionListener;
9+
import org.elasticsearch.cluster.metadata.IndexMetaData;
10+
import org.elasticsearch.cluster.metadata.MappingMetaData;
11+
import org.elasticsearch.cluster.service.ClusterService;
912
import org.elasticsearch.common.inject.Inject;
1013
import org.elasticsearch.common.settings.Settings;
1114
import org.elasticsearch.license.XPackLicenseState;
1215
import org.elasticsearch.xpack.core.XPackFeatureSet;
1316
import org.elasticsearch.xpack.core.XPackField;
1417
import org.elasticsearch.xpack.core.XPackSettings;
1518
import org.elasticsearch.xpack.core.vectors.VectorsFeatureSetUsage;
19+
import org.elasticsearch.xpack.vectors.mapper.DenseVectorFieldMapper;
20+
import org.elasticsearch.xpack.vectors.mapper.SparseVectorFieldMapper;
1621

1722
import java.util.Map;
1823

1924
public class VectorsFeatureSet implements XPackFeatureSet {
2025

2126
private final boolean enabled;
2227
private final XPackLicenseState licenseState;
28+
private final ClusterService clusterService;
2329

2430
@Inject
25-
public VectorsFeatureSet(Settings settings, XPackLicenseState licenseState) {
31+
public VectorsFeatureSet(Settings settings, XPackLicenseState licenseState, ClusterService clusterService) {
2632
this.enabled = XPackSettings.VECTORS_ENABLED.get(settings);
2733
this.licenseState = licenseState;
34+
this.clusterService = clusterService;
2835
}
2936

3037
@Override
@@ -49,6 +56,40 @@ public Map<String, Object> nativeCodeInfo() {
4956

5057
@Override
5158
public void usage(ActionListener<XPackFeatureSet.Usage> listener) {
52-
listener.onResponse(new VectorsFeatureSetUsage(available(), enabled()));
59+
boolean vectorsAvailable = available();
60+
boolean vectorsEnabled = enabled();
61+
int numDenseVectorFields = 0;
62+
int numSparseVectorFields = 0;
63+
int avgDenseVectorDims = 0;
64+
65+
if (vectorsAvailable && vectorsEnabled && clusterService.state() != null) {
66+
for (IndexMetaData indexMetaData : clusterService.state().metaData()) {
67+
MappingMetaData mappingMetaData = indexMetaData.mapping();
68+
if (mappingMetaData != null) {
69+
Map<String, Object> mappings = mappingMetaData.getSourceAsMap();
70+
if (mappings.containsKey("properties")) {
71+
@SuppressWarnings("unchecked") Map<String, Map<String, Object>> fieldMappings =
72+
(Map<String, Map<String, Object>>) mappings.get("properties");
73+
for (Map<String, Object> typeDefinition : fieldMappings.values()) {
74+
String fieldType = (String) typeDefinition.get("type");
75+
if (fieldType != null) {
76+
if (fieldType.equals(DenseVectorFieldMapper.CONTENT_TYPE)) {
77+
numDenseVectorFields++;
78+
int dims = (Integer) typeDefinition.get("dims");
79+
avgDenseVectorDims += dims;
80+
} else if (fieldType.equals(SparseVectorFieldMapper.CONTENT_TYPE)) {
81+
numSparseVectorFields++;
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
if (numDenseVectorFields > 0) {
89+
avgDenseVectorDims = avgDenseVectorDims / numDenseVectorFields;
90+
}
91+
}
92+
listener.onResponse(new VectorsFeatureSetUsage(vectorsAvailable, vectorsEnabled,
93+
numDenseVectorFields, numSparseVectorFields, avgDenseVectorDims));
5394
}
5495
}

x-pack/plugin/vectors/src/test/java/org/elasticsearch/xpack/vectors/VectorsFeatureSetTests.java

+48-9
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,51 @@
55
*/
66
package org.elasticsearch.xpack.vectors;
77

8+
import org.elasticsearch.Version;
89
import org.elasticsearch.action.support.PlainActionFuture;
10+
import org.elasticsearch.cluster.ClusterName;
11+
import org.elasticsearch.cluster.ClusterState;
12+
import org.elasticsearch.cluster.metadata.IndexMetaData;
13+
import org.elasticsearch.cluster.metadata.MetaData;
14+
import org.elasticsearch.cluster.service.ClusterService;
915
import org.elasticsearch.common.io.stream.BytesStreamOutput;
1016
import org.elasticsearch.common.settings.Settings;
1117
import org.elasticsearch.license.XPackLicenseState;
1218
import org.elasticsearch.test.ESTestCase;
1319
import org.elasticsearch.xpack.core.XPackFeatureSet;
1420
import org.elasticsearch.xpack.core.vectors.VectorsFeatureSetUsage;
1521
import org.junit.Before;
22+
import org.mockito.Mockito;
1623

17-
import static org.hamcrest.core.Is.is;
1824
import static org.mockito.Mockito.mock;
1925
import static org.mockito.Mockito.when;
2026

2127
public class VectorsFeatureSetTests extends ESTestCase {
2228

2329
private XPackLicenseState licenseState;
30+
private ClusterService clusterService;
2431

2532
@Before
2633
public void init() {
2734
licenseState = mock(XPackLicenseState.class);
35+
clusterService = mock(ClusterService.class);
2836
}
2937

3038
public void testAvailable() throws Exception {
31-
VectorsFeatureSet featureSet = new VectorsFeatureSet(Settings.EMPTY, licenseState);
39+
VectorsFeatureSet featureSet = new VectorsFeatureSet(Settings.EMPTY, licenseState, clusterService);
3240
boolean available = randomBoolean();
3341
when(licenseState.isVectorsAllowed()).thenReturn(available);
34-
assertThat(featureSet.available(), is(available));
42+
assertEquals(available, featureSet.available());
3543

3644
PlainActionFuture<XPackFeatureSet.Usage> future = new PlainActionFuture<>();
3745
featureSet.usage(future);
3846
XPackFeatureSet.Usage usage = future.get();
39-
assertThat(usage.available(), is(available));
47+
assertEquals(available, usage.available());
4048

4149
BytesStreamOutput out = new BytesStreamOutput();
4250
usage.writeTo(out);
4351
XPackFeatureSet.Usage serializedUsage = new VectorsFeatureSetUsage(out.bytes().streamInput());
44-
assertThat(serializedUsage.available(), is(available));
52+
assertEquals(available, serializedUsage.available());
4553
}
4654

4755
public void testEnabled() throws Exception {
@@ -54,17 +62,48 @@ public void testEnabled() throws Exception {
5462
} else {
5563
settings.put("xpack.vectors.enabled", enabled);
5664
}
57-
VectorsFeatureSet featureSet = new VectorsFeatureSet(settings.build(), licenseState);
58-
assertThat(featureSet.enabled(), is(enabled));
65+
VectorsFeatureSet featureSet = new VectorsFeatureSet(settings.build(), licenseState, clusterService);
66+
assertEquals(enabled, featureSet.enabled());
5967
PlainActionFuture<XPackFeatureSet.Usage> future = new PlainActionFuture<>();
6068
featureSet.usage(future);
6169
XPackFeatureSet.Usage usage = future.get();
62-
assertThat(usage.enabled(), is(enabled));
70+
assertEquals(enabled, usage.enabled());
6371

6472
BytesStreamOutput out = new BytesStreamOutput();
6573
usage.writeTo(out);
6674
XPackFeatureSet.Usage serializedUsage = new VectorsFeatureSetUsage(out.bytes().streamInput());
67-
assertThat(serializedUsage.enabled(), is(enabled));
75+
assertEquals(enabled, serializedUsage.enabled());
6876
}
6977

78+
public void testUsageStats() throws Exception {
79+
MetaData.Builder metaData = MetaData.builder();
80+
IndexMetaData.Builder index1 = IndexMetaData.builder("test-index1")
81+
.settings(settings(Version.CURRENT)).numberOfShards(1).numberOfReplicas(0)
82+
.putMapping("_doc",
83+
"{\"properties\":{\"my_dense_vector1\":{\"type\":\"dense_vector\",\"dims\": 10}," +
84+
"\"my_dense_vector2\":{\"type\":\"dense_vector\",\"dims\": 30} }}");
85+
IndexMetaData.Builder index2 = IndexMetaData.builder("test-index2")
86+
.settings(settings(Version.CURRENT)).numberOfShards(1).numberOfReplicas(0)
87+
.putMapping("_doc",
88+
"{\"properties\":{\"my_dense_vector3\":{\"type\":\"dense_vector\",\"dims\": 20}," +
89+
"\"my_sparse_vector1\":{\"type\":\"sparse_vector\"} }}");
90+
metaData.put(index1);
91+
metaData.put(index2);
92+
ClusterState clusterState = ClusterState.builder(new ClusterName("_testcluster")).metaData(metaData).build();
93+
94+
Mockito.when(clusterService.state()).thenReturn(clusterState);
95+
when(licenseState.isVectorsAllowed()).thenReturn(true);
96+
Settings.Builder settings = Settings.builder();
97+
settings.put("xpack.vectors.enabled", true);
98+
99+
PlainActionFuture<XPackFeatureSet.Usage> future = new PlainActionFuture<>();
100+
VectorsFeatureSet vectorsFeatureSet = new VectorsFeatureSet(settings.build(), licenseState, clusterService);
101+
vectorsFeatureSet.usage(future);
102+
VectorsFeatureSetUsage vectorUsage = (VectorsFeatureSetUsage) future.get();
103+
assertEquals(true, vectorUsage.enabled());
104+
assertEquals(true, vectorUsage.available());
105+
assertEquals(3, vectorUsage.numDenseVectorFields());
106+
assertEquals(1, vectorUsage.numSparseVectorFields());
107+
assertEquals(20, vectorUsage.avgDenseVectorDims());
108+
}
70109
}

0 commit comments

Comments
 (0)