Skip to content

Commit

Permalink
Inference telemetry (elastic#102877)
Browse files Browse the repository at this point in the history
* Empty infenrece usage wiring.

* Add fake data

* Fix NPE for secretSettings == null

* Real inference model stats

* New transport version

* Code polish

* Lint fixes

* Update docs/changelog/102877.yaml

* Update 102877.yaml

* Add inference to yamlRestTest

* Declare inference usage action as non-operator

* TransportInferenceUsageActionTests

* Lint fixes

* Replace map by ToXContentObject/Writeable

* Polish code

* AbstractWireSerializingTestCase<InferenceFeatureSetUsage.ModelStats>

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
jan-elastic and elasticmachine authored Dec 4, 2023
1 parent 84dad02 commit a67d5b8
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs/changelog/102877.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 102877
summary: Add basic telelemetry for the inference feature
area: Machine Learning
type: enhancement
issues: []
5 changes: 5 additions & 0 deletions docs/reference/rest-api/usage.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ GET /_xpack/usage
},
"node_count" : 1
},
"inference": {
"available" : true,
"enabled" : true,
"models" : []
},
"logstash" : {
"available" : true,
"enabled" : true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_PROFILE = def(8_551_00_0);
public static final TransportVersion CLUSTER_STATS_RESCORER_USAGE_ADDED = def(8_552_00_0);
public static final TransportVersion ML_INFERENCE_HF_SERVICE_ADDED = def(8_553_00_0);
public static final TransportVersion INFERENCE_USAGE_ADDED = def(8_554_00_0);
/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugin/core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
exports org.elasticsearch.xpack.core.indexing;
exports org.elasticsearch.xpack.core.inference.action;
exports org.elasticsearch.xpack.core.inference.results;
exports org.elasticsearch.xpack.core.inference;
exports org.elasticsearch.xpack.core.logstash;
exports org.elasticsearch.xpack.core.ml.action;
exports org.elasticsearch.xpack.core.ml.annotations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.elasticsearch.xpack.core.ilm.TimeseriesLifecycleType;
import org.elasticsearch.xpack.core.ilm.UnfollowAction;
import org.elasticsearch.xpack.core.ilm.WaitForSnapshotAction;
import org.elasticsearch.xpack.core.inference.InferenceFeatureSetUsage;
import org.elasticsearch.xpack.core.logstash.LogstashFeatureSetUsage;
import org.elasticsearch.xpack.core.ml.MachineLearningFeatureSetUsage;
import org.elasticsearch.xpack.core.ml.MlMetadata;
Expand Down Expand Up @@ -133,6 +134,8 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.LOGSTASH, LogstashFeatureSetUsage::new),
// ML
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.MACHINE_LEARNING, MachineLearningFeatureSetUsage::new),
// inference
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.INFERENCE, InferenceFeatureSetUsage::new),
// monitoring
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.MONITORING, MonitoringFeatureSetUsage::new),
// security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public final class XPackField {
public static final String GRAPH = "graph";
/** Name constant for the machine learning feature. */
public static final String MACHINE_LEARNING = "ml";
/** Name constant for the inference feature. */
public static final String INFERENCE = "inference";
/** Name constant for the Logstash feature. */
public static final String LOGSTASH = "logstash";
/** Name constant for the Beats feature. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
public static final XPackUsageFeatureAction WATCHER = new XPackUsageFeatureAction(XPackField.WATCHER);
public static final XPackUsageFeatureAction GRAPH = new XPackUsageFeatureAction(XPackField.GRAPH);
public static final XPackUsageFeatureAction MACHINE_LEARNING = new XPackUsageFeatureAction(XPackField.MACHINE_LEARNING);
public static final XPackUsageFeatureAction INFERENCE = new XPackUsageFeatureAction(XPackField.INFERENCE);
public static final XPackUsageFeatureAction LOGSTASH = new XPackUsageFeatureAction(XPackField.LOGSTASH);
public static final XPackUsageFeatureAction EQL = new XPackUsageFeatureAction(XPackField.EQL);
public static final XPackUsageFeatureAction ESQL = new XPackUsageFeatureAction(XPackField.ESQL);
Expand Down Expand Up @@ -64,6 +65,7 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
FROZEN_INDICES,
GRAPH,
INDEX_LIFECYCLE,
INFERENCE,
LOGSTASH,
MACHINE_LEARNING,
MONITORING,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.inference;

import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.inference.TaskType;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.XPackFeatureSet;
import org.elasticsearch.xpack.core.XPackField;

import java.io.IOException;
import java.util.Collection;
import java.util.Objects;

public class InferenceFeatureSetUsage extends XPackFeatureSet.Usage {

public static class ModelStats implements ToXContentObject, Writeable {

private final String service;
private final TaskType taskType;
private long count;

public ModelStats(String service, TaskType taskType) {
this(service, taskType, 0L);
}

public ModelStats(String service, TaskType taskType, long count) {
this.service = service;
this.taskType = taskType;
this.count = count;
}

public ModelStats(ModelStats stats) {
this(stats.service, stats.taskType, stats.count);
}

public ModelStats(StreamInput in) throws IOException {
this.service = in.readString();
this.taskType = in.readEnum(TaskType.class);
this.count = in.readLong();
}

public void add() {
count++;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("service", service);
builder.field("task_type", taskType.name());
builder.field("count", count);
builder.endObject();
return builder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(service);
out.writeEnum(taskType);
out.writeLong(count);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelStats that = (ModelStats) o;
return count == that.count && Objects.equals(service, that.service) && taskType == that.taskType;
}

@Override
public int hashCode() {
return Objects.hash(service, taskType, count);
}
}

private final Collection<ModelStats> modelStats;

public InferenceFeatureSetUsage(Collection<ModelStats> modelStats) {
super(XPackField.INFERENCE, true, true);
this.modelStats = modelStats;
}

public InferenceFeatureSetUsage(StreamInput in) throws IOException {
super(in);
this.modelStats = in.readCollectionAsList(ModelStats::new);
}

@Override
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
super.innerXContent(builder, params);
builder.xContentList("models", modelStats);
}

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

@Override
public TransportVersion getMinimalSupportedVersion() {
return TransportVersions.INFERENCE_USAGE_ADDED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.inference;

import com.carrotsearch.randomizedtesting.generators.RandomStrings;

import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.inference.TaskType;
import org.elasticsearch.test.AbstractWireSerializingTestCase;

import java.io.IOException;

public class InferenceFeatureSetUsageTests extends AbstractWireSerializingTestCase<InferenceFeatureSetUsage.ModelStats> {

@Override
protected Writeable.Reader<InferenceFeatureSetUsage.ModelStats> instanceReader() {
return InferenceFeatureSetUsage.ModelStats::new;
}

@Override
protected InferenceFeatureSetUsage.ModelStats createTestInstance() {
RandomStrings.randomAsciiLettersOfLength(random(), 10);
return new InferenceFeatureSetUsage.ModelStats(
randomIdentifier(),
TaskType.values()[randomInt(TaskType.values().length - 1)],
randomInt(10)
);
}

@Override
protected InferenceFeatureSetUsage.ModelStats mutateInstance(InferenceFeatureSetUsage.ModelStats modelStats) throws IOException {
InferenceFeatureSetUsage.ModelStats newModelStats = new InferenceFeatureSetUsage.ModelStats(modelStats);
newModelStats.add();
return newModelStats;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@
import org.elasticsearch.threadpool.ExecutorBuilder;
import org.elasticsearch.threadpool.ScalingExecutorBuilder;
import org.elasticsearch.xpack.core.ClientHelper;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
import org.elasticsearch.xpack.core.inference.action.DeleteInferenceModelAction;
import org.elasticsearch.xpack.core.inference.action.GetInferenceModelAction;
import org.elasticsearch.xpack.core.inference.action.InferenceAction;
import org.elasticsearch.xpack.core.inference.action.PutInferenceModelAction;
import org.elasticsearch.xpack.inference.action.TransportDeleteInferenceModelAction;
import org.elasticsearch.xpack.inference.action.TransportGetInferenceModelAction;
import org.elasticsearch.xpack.inference.action.TransportInferenceAction;
import org.elasticsearch.xpack.inference.action.TransportInferenceUsageAction;
import org.elasticsearch.xpack.inference.action.TransportPutInferenceModelAction;
import org.elasticsearch.xpack.inference.external.http.HttpClientManager;
import org.elasticsearch.xpack.inference.external.http.HttpSettings;
Expand Down Expand Up @@ -86,7 +88,8 @@ public InferencePlugin(Settings settings) {
new ActionHandler<>(InferenceAction.INSTANCE, TransportInferenceAction.class),
new ActionHandler<>(GetInferenceModelAction.INSTANCE, TransportGetInferenceModelAction.class),
new ActionHandler<>(PutInferenceModelAction.INSTANCE, TransportPutInferenceModelAction.class),
new ActionHandler<>(DeleteInferenceModelAction.INSTANCE, TransportDeleteInferenceModelAction.class)
new ActionHandler<>(DeleteInferenceModelAction.INSTANCE, TransportDeleteInferenceModelAction.class),
new ActionHandler<>(XPackUsageFeatureAction.INFERENCE, TransportInferenceUsageAction.class)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.inference.action;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.client.internal.OriginSettingClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.inference.ModelConfigurations;
import org.elasticsearch.inference.TaskType;
import org.elasticsearch.protocol.xpack.XPackUsageRequest;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureResponse;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureTransportAction;
import org.elasticsearch.xpack.core.inference.InferenceFeatureSetUsage;
import org.elasticsearch.xpack.core.inference.action.GetInferenceModelAction;

import java.util.Map;
import java.util.TreeMap;

import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;

public class TransportInferenceUsageAction extends XPackUsageFeatureTransportAction {

private final Client client;

@Inject
public TransportInferenceUsageAction(
TransportService transportService,
ClusterService clusterService,
ThreadPool threadPool,
ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
Client client
) {
super(
XPackUsageFeatureAction.INFERENCE.name(),
transportService,
clusterService,
threadPool,
actionFilters,
indexNameExpressionResolver
);
this.client = new OriginSettingClient(client, ML_ORIGIN);
}

@Override
protected void masterOperation(
Task task,
XPackUsageRequest request,
ClusterState state,
ActionListener<XPackUsageFeatureResponse> listener
) throws Exception {
GetInferenceModelAction.Request getInferenceModelAction = new GetInferenceModelAction.Request("_all", TaskType.ANY);
client.execute(GetInferenceModelAction.INSTANCE, getInferenceModelAction, ActionListener.wrap(response -> {
Map<String, InferenceFeatureSetUsage.ModelStats> stats = new TreeMap<>();
for (ModelConfigurations model : response.getModels()) {
String statKey = model.getService() + ":" + model.getTaskType().name();
InferenceFeatureSetUsage.ModelStats stat = stats.computeIfAbsent(
statKey,
key -> new InferenceFeatureSetUsage.ModelStats(model.getService(), model.getTaskType())
);
stat.add();
}
InferenceFeatureSetUsage usage = new InferenceFeatureSetUsage(stats.values());
listener.onResponse(new XPackUsageFeatureResponse(usage));
}, listener::onFailure));
}
}
Loading

0 comments on commit a67d5b8

Please sign in to comment.