Browse Source

Telemetry data initial implementation (#51715)

Andrei Stefan 5 years ago
parent
commit
f1d1cceaca
18 changed files with 723 additions and 6 deletions
  1. 6 0
      docs/reference/rest-api/info.asciidoc
  2. 9 0
      x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java
  3. 4 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
  4. 2 0
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java
  5. 2 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoFeatureAction.java
  6. 2 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackUsageFeatureAction.java
  7. 51 0
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/eql/EqlFeatureSetUsage.java
  8. 46 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/EqlInfoTransportAction.java
  9. 75 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/EqlUsageTransportAction.java
  10. 10 3
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlPlugin.java
  11. 19 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsAction.java
  12. 73 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsRequest.java
  13. 101 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsResponse.java
  14. 34 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlStatsAction.java
  15. 62 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlStatsAction.java
  16. 20 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/stats/FeatureMetric.java
  17. 85 0
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/stats/Metrics.java
  18. 122 0
      x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/EqlInfoTransportActionTests.java

+ 6 - 0
docs/reference/rest-api/info.asciidoc

@@ -119,6 +119,10 @@ Example response:
          "available" : true,
          "enabled" : true
       },
+      "eql" : {
+         "available" : true,
+         "enabled" : true
+      },
       "sql" : {
          "available" : true,
          "enabled" : true
@@ -149,6 +153,8 @@ Example response:
 // TESTRESPONSE[s/"expiry_date_in_millis" : 1542665112332/"expiry_date_in_millis" : "$body.license.expiry_date_in_millis"/]
 // TESTRESPONSE[s/"version" : "7.0.0-alpha1-SNAPSHOT",/"version": "$body.features.ml.native_code_info.version",/]
 // TESTRESPONSE[s/"build_hash" : "99a07c016d5a73"/"build_hash": "$body.features.ml.native_code_info.build_hash"/]
+// TESTRESPONSE[s/"eql" : \{[^\}]*\},/"eql": $body.$_path,/]
+// eql is disabled by default on release builds and enabled everywhere else during the initial implementation phase until its release
 // So much s/// but at least we test that the layout is close to matching....
 
 The following example only returns the build and features information:

+ 9 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java

@@ -706,6 +706,15 @@ public class XPackLicenseState {
         return localStatus.active;
     }
 
+    /**
+     * Determine if EQL support should be enabled.
+     * <p>
+     *  EQL is available for all license types except {@link OperationMode#MISSING}
+     */
+    public synchronized boolean isEqlAllowed() {
+        return status.active;
+    }
+
     /**
      * Determine if SQL support should be enabled.
      * <p>

+ 4 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java

@@ -37,6 +37,7 @@ import org.elasticsearch.xpack.core.analytics.AnalyticsFeatureSetUsage;
 import org.elasticsearch.xpack.core.beats.BeatsFeatureSetUsage;
 import org.elasticsearch.xpack.core.ccr.AutoFollowMetadata;
 import org.elasticsearch.xpack.core.deprecation.DeprecationInfoAction;
+import org.elasticsearch.xpack.core.eql.EqlFeatureSetUsage;
 import org.elasticsearch.xpack.core.flattened.FlattenedFeatureSetUsage;
 import org.elasticsearch.xpack.core.frozen.FrozenIndicesFeatureSetUsage;
 import org.elasticsearch.xpack.core.frozen.action.FreezeIndexAction;
@@ -54,9 +55,9 @@ import org.elasticsearch.xpack.core.ilm.ReadOnlyAction;
 import org.elasticsearch.xpack.core.ilm.RolloverAction;
 import org.elasticsearch.xpack.core.ilm.SetPriorityAction;
 import org.elasticsearch.xpack.core.ilm.ShrinkAction;
-import org.elasticsearch.xpack.core.ilm.WaitForSnapshotAction;
 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.ilm.action.DeleteLifecycleAction;
 import org.elasticsearch.xpack.core.ilm.action.ExplainLifecycleAction;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
@@ -500,6 +501,8 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl
                 new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AnyExpression.NAME, AnyExpression::new),
                 new NamedWriteableRegistry.Entry(RoleMapperExpression.class, FieldExpression.NAME, FieldExpression::new),
                 new NamedWriteableRegistry.Entry(RoleMapperExpression.class, ExceptExpression.NAME, ExceptExpression::new),
+                // eql
+                new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.EQL, EqlFeatureSetUsage::new),
                 // sql
                 new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SQL, SqlFeatureSetUsage::new),
                 // watcher

+ 2 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java

@@ -27,6 +27,8 @@ public final class XPackField {
     public static final String UPGRADE = "upgrade";
     // inside of YAML settings we still use xpack do not having handle issues with dashes
     public static final String SETTINGS_NAME = "xpack";
+    /** Name constant for the eql feature. */
+    public static final String EQL = "eql";
     /** Name constant for the sql feature. */
     public static final String SQL = "sql";
     /** Name constant for the rollup feature. */

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackInfoFeatureAction.java

@@ -28,6 +28,7 @@ public class XPackInfoFeatureAction extends ActionType<XPackInfoFeatureResponse>
     public static final XPackInfoFeatureAction GRAPH = new XPackInfoFeatureAction(XPackField.GRAPH);
     public static final XPackInfoFeatureAction MACHINE_LEARNING = new XPackInfoFeatureAction(XPackField.MACHINE_LEARNING);
     public static final XPackInfoFeatureAction LOGSTASH = new XPackInfoFeatureAction(XPackField.LOGSTASH);
+    public static final XPackInfoFeatureAction EQL = new XPackInfoFeatureAction(XPackField.EQL);
     public static final XPackInfoFeatureAction SQL = new XPackInfoFeatureAction(XPackField.SQL);
     public static final XPackInfoFeatureAction ROLLUP = new XPackInfoFeatureAction(XPackField.ROLLUP);
     public static final XPackInfoFeatureAction INDEX_LIFECYCLE = new XPackInfoFeatureAction(XPackField.INDEX_LIFECYCLE);
@@ -43,7 +44,7 @@ public class XPackInfoFeatureAction extends ActionType<XPackInfoFeatureResponse>
     public static final XPackInfoFeatureAction ENRICH = new XPackInfoFeatureAction(XPackField.ENRICH);
 
     public static final List<XPackInfoFeatureAction> ALL = Arrays.asList(
-        SECURITY, MONITORING, WATCHER, GRAPH, MACHINE_LEARNING, LOGSTASH, SQL, ROLLUP, INDEX_LIFECYCLE, SNAPSHOT_LIFECYCLE, CCR,
+        SECURITY, MONITORING, WATCHER, GRAPH, MACHINE_LEARNING, LOGSTASH, EQL, SQL, ROLLUP, INDEX_LIFECYCLE, SNAPSHOT_LIFECYCLE, CCR,
         TRANSFORM, FLATTENED, VECTORS, VOTING_ONLY, FROZEN_INDICES, SPATIAL, ANALYTICS, ENRICH
     );
 

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/XPackUsageFeatureAction.java

@@ -28,6 +28,7 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
     public static final XPackUsageFeatureAction GRAPH = new XPackUsageFeatureAction(XPackField.GRAPH);
     public static final XPackUsageFeatureAction MACHINE_LEARNING = new XPackUsageFeatureAction(XPackField.MACHINE_LEARNING);
     public static final XPackUsageFeatureAction LOGSTASH = new XPackUsageFeatureAction(XPackField.LOGSTASH);
+    public static final XPackUsageFeatureAction EQL = new XPackUsageFeatureAction(XPackField.EQL);
     public static final XPackUsageFeatureAction SQL = new XPackUsageFeatureAction(XPackField.SQL);
     public static final XPackUsageFeatureAction ROLLUP = new XPackUsageFeatureAction(XPackField.ROLLUP);
     public static final XPackUsageFeatureAction INDEX_LIFECYCLE = new XPackUsageFeatureAction(XPackField.INDEX_LIFECYCLE);
@@ -42,7 +43,7 @@ public class XPackUsageFeatureAction extends ActionType<XPackUsageFeatureRespons
     public static final XPackUsageFeatureAction ANALYTICS = new XPackUsageFeatureAction(XPackField.ANALYTICS);
 
     public static final List<XPackUsageFeatureAction> ALL = Arrays.asList(
-        SECURITY, MONITORING, WATCHER, GRAPH, MACHINE_LEARNING, LOGSTASH, SQL, ROLLUP, INDEX_LIFECYCLE, SNAPSHOT_LIFECYCLE, CCR,
+        SECURITY, MONITORING, WATCHER, GRAPH, MACHINE_LEARNING, LOGSTASH, EQL, SQL, ROLLUP, INDEX_LIFECYCLE, SNAPSHOT_LIFECYCLE, CCR,
         TRANSFORM, FLATTENED, VECTORS, VOTING_ONLY, FROZEN_INDICES, SPATIAL, ANALYTICS
     );
 

+ 51 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/eql/EqlFeatureSetUsage.java

@@ -0,0 +1,51 @@
+/*
+ * 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.eql;
+
+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.Map;
+
+public class EqlFeatureSetUsage extends XPackFeatureSet.Usage {
+    
+    private final Map<String, Object> stats;
+
+    public EqlFeatureSetUsage(StreamInput in) throws IOException {
+        super(in);
+        stats = in.readMap();
+    }
+
+    public EqlFeatureSetUsage(boolean available, boolean enabled, Map<String, Object> stats) {
+        super(XPackField.EQL, available, enabled);
+        this.stats = stats;
+    }
+
+    public Map<String, Object> stats() {
+        return stats;
+    }
+
+    @Override
+    protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
+        super.innerXContent(builder, params);
+        if (enabled) {
+            for (Map.Entry<String, Object> entry : stats.entrySet()) {
+                builder.field(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        super.writeTo(out);
+        out.writeMap(stats);
+    }
+}

+ 46 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/EqlInfoTransportAction.java

@@ -0,0 +1,46 @@
+/*
+ * 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.eql;
+
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.XPackField;
+import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction;
+import org.elasticsearch.xpack.core.action.XPackInfoFeatureTransportAction;
+import org.elasticsearch.xpack.eql.plugin.EqlPlugin;
+
+public class EqlInfoTransportAction extends XPackInfoFeatureTransportAction {
+
+    private final boolean enabled;
+    private final XPackLicenseState licenseState;
+    
+    @Inject
+    public EqlInfoTransportAction(TransportService transportService, ActionFilters actionFilters,
+                                  Settings settings, XPackLicenseState licenseState) {
+        super(XPackInfoFeatureAction.EQL.name(), transportService, actionFilters);
+        this.enabled = EqlPlugin.isEnabled(settings);
+        this.licenseState = licenseState;
+    }
+    
+    @Override
+    public String name() {
+        return XPackField.EQL;
+    }
+
+    @Override
+    public boolean available() {
+        return licenseState.isEqlAllowed();
+    }
+
+    @Override
+    public boolean enabled() {
+        return enabled;
+    }
+
+}

+ 75 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/EqlUsageTransportAction.java

@@ -0,0 +1,75 @@
+/*
+ * 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.eql;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.client.Client;
+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.common.settings.Settings;
+import org.elasticsearch.license.XPackLicenseState;
+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.eql.EqlFeatureSetUsage;
+import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
+import org.elasticsearch.xpack.eql.plugin.EqlPlugin;
+import org.elasticsearch.xpack.eql.plugin.EqlStatsAction;
+import org.elasticsearch.xpack.eql.plugin.EqlStatsRequest;
+import org.elasticsearch.xpack.eql.plugin.EqlStatsResponse;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class EqlUsageTransportAction extends XPackUsageFeatureTransportAction {
+    private final boolean enabled;
+    private final XPackLicenseState licenseState;
+    private final Client client;
+
+    @Inject
+    public EqlUsageTransportAction(TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
+                                   ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
+                                   Settings settings, XPackLicenseState licenseState, Client client) {
+        super(XPackUsageFeatureAction.EQL.name(), transportService, clusterService, threadPool, actionFilters,
+            indexNameExpressionResolver);
+        this.enabled = EqlPlugin.isEnabled(settings);
+        this.licenseState = licenseState;
+        this.client = client;
+    }
+
+    @Override
+    protected void masterOperation(Task task, XPackUsageRequest request, ClusterState state,
+                                   ActionListener<XPackUsageFeatureResponse> listener) {
+        boolean available = licenseState.isEqlAllowed();
+        if (enabled) {
+            EqlStatsRequest eqlRequest = new EqlStatsRequest();
+            eqlRequest.includeStats(true);
+            eqlRequest.setParentTask(clusterService.localNode().getId(), task.getId());
+            client.execute(EqlStatsAction.INSTANCE, eqlRequest, ActionListener.wrap(r -> {
+                List<Counters> countersPerNode = r.getNodes()
+                    .stream()
+                    .map(EqlStatsResponse.NodeStatsResponse::getStats)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+                Counters mergedCounters = Counters.merge(countersPerNode);
+                EqlFeatureSetUsage usage = new EqlFeatureSetUsage(available, enabled, mergedCounters.toNestedMap());
+                listener.onResponse(new XPackUsageFeatureResponse(usage));
+            }, listener::onFailure));
+        } else {
+            EqlFeatureSetUsage usage = new EqlFeatureSetUsage(available, enabled, Collections.emptyMap());
+            listener.onResponse(new XPackUsageFeatureResponse(usage));
+        }
+    }
+}

+ 10 - 3
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlPlugin.java

@@ -28,6 +28,10 @@ import org.elasticsearch.rest.RestHandler;
 import org.elasticsearch.script.ScriptService;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.watcher.ResourceWatcherService;
+import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction;
+import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
+import org.elasticsearch.xpack.eql.EqlInfoTransportAction;
+import org.elasticsearch.xpack.eql.EqlUsageTransportAction;
 import org.elasticsearch.xpack.eql.action.EqlSearchAction;
 import org.elasticsearch.xpack.eql.execution.PlanExecutor;
 import org.elasticsearch.xpack.ql.index.IndexResolver;
@@ -65,7 +69,10 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
     @Override
     public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
         return Arrays.asList(
-            new ActionHandler<>(EqlSearchAction.INSTANCE, TransportEqlSearchAction.class)
+            new ActionHandler<>(EqlSearchAction.INSTANCE, TransportEqlSearchAction.class),
+            new ActionHandler<>(EqlStatsAction.INSTANCE, TransportEqlStatsAction.class),
+            new ActionHandler<>(XPackUsageFeatureAction.EQL, EqlUsageTransportAction.class),
+            new ActionHandler<>(XPackInfoFeatureAction.EQL, EqlInfoTransportAction.class)
         );
     }
 
@@ -88,7 +95,7 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
     }
 
     // TODO: this needs to be used by all plugin methods - including getActions and createComponents
-    private boolean isEnabled(Settings settings) {
+    public static boolean isEnabled(Settings settings) {
         return EQL_ENABLED_SETTING.get(settings);
     }
 
@@ -104,6 +111,6 @@ public class EqlPlugin extends Plugin implements ActionPlugin {
         if (isEnabled(settings) == false) {
             return Collections.emptyList();
         }
-        return Arrays.asList(new RestEqlSearchAction(restController));
+        return Arrays.asList(new RestEqlSearchAction(restController), new RestEqlStatsAction(restController));
     }
 }

+ 19 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsAction.java

@@ -0,0 +1,19 @@
+/*
+ * 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.eql.plugin;
+
+import org.elasticsearch.action.ActionType;
+
+public class EqlStatsAction extends ActionType<EqlStatsResponse> {
+
+    public static final EqlStatsAction INSTANCE = new EqlStatsAction();
+    public static final String NAME = "cluster:monitor/xpack/eql/stats/dist";
+
+    private EqlStatsAction() {
+        super(NAME, EqlStatsResponse::new);
+    }
+}

+ 73 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsRequest.java

@@ -0,0 +1,73 @@
+/*
+ * 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.eql.plugin;
+
+import org.elasticsearch.action.support.nodes.BaseNodeRequest;
+import org.elasticsearch.action.support.nodes.BaseNodesRequest;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+
+import java.io.IOException;
+
+/**
+ * Request to gather usage statistics
+ */
+public class EqlStatsRequest extends BaseNodesRequest<EqlStatsRequest> {
+    
+    private boolean includeStats;
+
+    public EqlStatsRequest() {
+        super((String[]) null);
+    }
+    
+    public EqlStatsRequest(StreamInput in) throws IOException {
+        super(in);
+        includeStats = in.readBoolean();
+    }
+    
+    public boolean includeStats() {
+        return includeStats;
+    }
+
+    public void includeStats(boolean includeStats) {
+        this.includeStats = includeStats;
+    }
+
+    @Override
+    public void writeTo(StreamOutput out) throws IOException {
+        super.writeTo(out);
+        out.writeBoolean(includeStats);
+    }
+    
+    @Override
+    public String toString() {
+        return "eql_stats";
+    }
+    
+    static class NodeStatsRequest extends BaseNodeRequest {
+        boolean includeStats;
+        
+        NodeStatsRequest(StreamInput in) throws IOException {
+            super(in);
+            includeStats = in.readBoolean();
+        }
+
+        NodeStatsRequest(EqlStatsRequest request) {
+            includeStats = request.includeStats();
+        }
+        
+        public boolean includeStats() {
+            return includeStats;
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            super.writeTo(out);
+            out.writeBoolean(includeStats);
+        }
+    }
+}

+ 101 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/EqlStatsResponse.java

@@ -0,0 +1,101 @@
+/*
+ * 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.eql.plugin;
+
+import org.elasticsearch.action.FailedNodeException;
+import org.elasticsearch.action.support.nodes.BaseNodeResponse;
+import org.elasticsearch.action.support.nodes.BaseNodesResponse;
+import org.elasticsearch.cluster.ClusterName;
+import org.elasticsearch.cluster.node.DiscoveryNode;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
+
+import java.io.IOException;
+import java.util.List;
+
+public class EqlStatsResponse extends BaseNodesResponse<EqlStatsResponse.NodeStatsResponse> implements ToXContentObject {
+    
+    public EqlStatsResponse(StreamInput in) throws IOException {
+        super(in);
+    }
+    
+    public EqlStatsResponse(ClusterName clusterName, List<NodeStatsResponse> nodes, List<FailedNodeException> failures) {
+        super(clusterName, nodes, failures);
+    }
+
+    @Override
+    protected List<NodeStatsResponse> readNodesFrom(StreamInput in) throws IOException {
+        return in.readList(NodeStatsResponse::readNodeResponse);
+    }
+
+    @Override
+    protected void writeNodesTo(StreamOutput out, List<NodeStatsResponse> nodes) throws IOException {
+        out.writeList(nodes);
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startArray("stats");
+        for (NodeStatsResponse node : getNodes()) {
+            node.toXContent(builder, params);
+        }
+        builder.endArray();
+
+        return builder;
+    }
+
+    public static class NodeStatsResponse extends BaseNodeResponse implements ToXContentObject {
+        
+        private Counters stats;
+        
+        public NodeStatsResponse(StreamInput in) throws IOException {
+            super(in);
+            if (in.readBoolean()) {
+                stats = new Counters(in);
+            }
+        }
+
+        public NodeStatsResponse(DiscoveryNode node) {
+            super(node);
+        }
+        
+        public Counters getStats() {
+            return stats;
+        }
+
+        public void setStats(Counters stats) {
+            this.stats = stats;
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            super.writeTo(out);
+            out.writeBoolean(stats != null);
+            if (stats != null) {
+                stats.writeTo(out);
+            }
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            if (stats != null && stats.hasCounters()) {
+                builder.field("stats", stats.toNestedMap());
+            }
+            builder.endObject();
+            return builder;
+        }
+        
+        static EqlStatsResponse.NodeStatsResponse readNodeResponse(StreamInput in) throws IOException {
+            return new EqlStatsResponse.NodeStatsResponse(in);
+        }
+
+    }
+}

+ 34 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/RestEqlStatsAction.java

@@ -0,0 +1,34 @@
+/*
+ * 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.eql.plugin;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestActions;
+
+import static org.elasticsearch.rest.RestRequest.Method.GET;
+
+public class RestEqlStatsAction extends BaseRestHandler {
+
+    protected RestEqlStatsAction(RestController controller) {
+        controller.registerHandler(GET, "/_eql/stats", this);
+    }
+
+    @Override
+    public String getName() {
+        return "eql_stats";
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) {
+        EqlStatsRequest request = new EqlStatsRequest();
+        return channel -> client.execute(EqlStatsAction.INSTANCE, request, new RestActions.NodesResponseRestListener<>(channel));
+    }
+
+}

+ 62 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/plugin/TransportEqlStatsAction.java

@@ -0,0 +1,62 @@
+/*
+ * 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.eql.plugin;
+
+import org.elasticsearch.action.FailedNodeException;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.nodes.TransportNodesAction;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Performs the stats operation.
+ */
+public class TransportEqlStatsAction extends TransportNodesAction<EqlStatsRequest, EqlStatsResponse,
+        EqlStatsRequest.NodeStatsRequest, EqlStatsResponse.NodeStatsResponse> {
+    
+    // the plan executor holds the metrics
+    //private final PlanExecutor planExecutor;
+
+    @Inject
+    public TransportEqlStatsAction(TransportService transportService, ClusterService clusterService,
+            ThreadPool threadPool, ActionFilters actionFilters/* , PlanExecutor planExecutor */) {
+        super(EqlStatsAction.NAME, threadPool, clusterService, transportService, actionFilters,
+              EqlStatsRequest::new, EqlStatsRequest.NodeStatsRequest::new, ThreadPool.Names.MANAGEMENT,
+              EqlStatsResponse.NodeStatsResponse.class);
+        //this.planExecutor = planExecutor;
+    }
+
+    @Override
+    protected EqlStatsResponse newResponse(EqlStatsRequest request, List<EqlStatsResponse.NodeStatsResponse> nodes,
+                                           List<FailedNodeException> failures) {
+        return new EqlStatsResponse(clusterService.getClusterName(), nodes, failures);
+    }
+
+    @Override
+    protected EqlStatsRequest.NodeStatsRequest newNodeRequest(EqlStatsRequest request) {
+        return new EqlStatsRequest.NodeStatsRequest(request);
+    }
+
+    @Override
+    protected EqlStatsResponse.NodeStatsResponse newNodeResponse(StreamInput in) throws IOException {
+        return new EqlStatsResponse.NodeStatsResponse(in);
+    }
+
+    @Override
+    protected EqlStatsResponse.NodeStatsResponse nodeOperation(EqlStatsRequest.NodeStatsRequest request, Task task) {
+        EqlStatsResponse.NodeStatsResponse statsResponse = new EqlStatsResponse.NodeStatsResponse(clusterService.localNode());
+        //statsResponse.setStats(planExecutor.metrics().stats());
+        
+        return statsResponse;
+    }
+}

+ 20 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/stats/FeatureMetric.java

@@ -0,0 +1,20 @@
+/*
+ * 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.eql.stats;
+
+import java.util.Locale;
+
+public enum FeatureMetric {
+    SEQUENCE,
+    JOIN,
+    PIPE;
+
+    @Override
+    public String toString() {
+        return this.name().toLowerCase(Locale.ROOT);
+    }
+}

+ 85 - 0
x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/stats/Metrics.java

@@ -0,0 +1,85 @@
+/*
+ * 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.eql.stats;
+
+import org.elasticsearch.common.metrics.CounterMetric;
+import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class encapsulating the metrics collected for EQL
+ */
+public class Metrics {
+    private enum OperationType {
+        FAILED, TOTAL;
+
+        @Override
+        public String toString() {
+            return this.name().toLowerCase(Locale.ROOT);
+        }
+    }
+    
+    // map that holds total/failed counters for each eql "feature" (join, pipe, sequence...)
+    private final Map<FeatureMetric, Map<OperationType, CounterMetric>> featuresMetrics;
+    protected static String FPREFIX = "features.";
+    
+    public Metrics() {
+        Map<FeatureMetric, Map<OperationType, CounterMetric>> fMap = new LinkedHashMap<>();
+        for (FeatureMetric metric : FeatureMetric.values()) {
+            Map<OperationType, CounterMetric> metricsMap = new LinkedHashMap<>(OperationType.values().length);
+            for (OperationType type : OperationType.values()) {
+                metricsMap.put(type,  new CounterMetric());
+            }
+            
+            fMap.put(metric, Collections.unmodifiableMap(metricsMap));
+        }
+        featuresMetrics = Collections.unmodifiableMap(fMap);
+    }
+
+    /**
+     * Increments the "total" counter for a metric
+     * This method should be called only once per query.
+     */
+    public void total(FeatureMetric metric) {
+        inc(metric, OperationType.TOTAL);
+    }
+    
+    /**
+     * Increments the "failed" counter for a metric
+     */
+    public void failed(FeatureMetric metric) {
+        inc(metric, OperationType.FAILED);
+    }
+
+    private void inc(FeatureMetric metric, OperationType op) {
+        this.featuresMetrics.get(metric).get(op).inc();
+    }
+
+    public Counters stats() {
+        Counters counters = new Counters();
+        
+        // queries metrics
+        for (Entry<FeatureMetric, Map<OperationType, CounterMetric>> entry : featuresMetrics.entrySet()) {
+            String metricName = entry.getKey().toString();
+            
+            for (OperationType type : OperationType.values()) {
+                long metricCounter = entry.getValue().get(type).count();
+                String operationTypeName = type.toString();
+                
+                counters.inc(FPREFIX + metricName + "." + operationTypeName, metricCounter);
+                counters.inc(FPREFIX + "_all." + operationTypeName, metricCounter);
+            }
+        }
+        
+        return counters;
+    }
+}

+ 122 - 0
x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/EqlInfoTransportActionTests.java

@@ -0,0 +1,122 @@
+/*
+ * 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.eql;
+
+import org.elasticsearch.Version;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.PlainActionFuture;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.cluster.ClusterName;
+import org.elasticsearch.cluster.node.DiscoveryNode;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.util.concurrent.ThreadContext;
+import org.elasticsearch.common.xcontent.ObjectPath;
+import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.action.XPackUsageFeatureResponse;
+import org.elasticsearch.xpack.core.eql.EqlFeatureSetUsage;
+import org.elasticsearch.xpack.core.watcher.common.stats.Counters;
+import org.elasticsearch.xpack.eql.plugin.EqlStatsAction;
+import org.elasticsearch.xpack.eql.plugin.EqlStatsResponse;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class EqlInfoTransportActionTests extends ESTestCase {
+
+    private XPackLicenseState licenseState;
+    private Client client;
+
+    @Before
+    public void init() throws Exception {
+        licenseState = mock(XPackLicenseState.class);
+        client = mock(Client.class);
+        ThreadPool threadPool = mock(ThreadPool.class);
+        ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
+        when(threadPool.getThreadContext()).thenReturn(threadContext);
+        when(client.threadPool()).thenReturn(threadPool);
+    }
+
+    public void testAvailable() {
+        EqlInfoTransportAction featureSet = new EqlInfoTransportAction(
+            mock(TransportService.class), mock(ActionFilters.class), Settings.EMPTY, licenseState);
+        boolean available = randomBoolean();
+        when(licenseState.isEqlAllowed()).thenReturn(available);
+        assertThat(featureSet.available(), is(available));
+    }
+
+    public void testEnabled() {
+        boolean enabled = randomBoolean();
+        Settings.Builder settings = Settings.builder();
+        settings.put("xpack.eql.enabled", enabled);
+        
+        EqlInfoTransportAction featureSet = new EqlInfoTransportAction(
+            mock(TransportService.class), mock(ActionFilters.class), settings.build(), licenseState);
+        assertThat(featureSet.enabled(), is(enabled));
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUsageStats() throws Exception {
+        doAnswer(mock -> {
+            ActionListener<EqlStatsResponse> listener =
+                    (ActionListener<EqlStatsResponse>) mock.getArguments()[2];
+
+            List<EqlStatsResponse.NodeStatsResponse> nodes = new ArrayList<>();
+            DiscoveryNode first = new DiscoveryNode("first", buildNewFakeTransportAddress(), Version.CURRENT);
+            EqlStatsResponse.NodeStatsResponse firstNode = new EqlStatsResponse.NodeStatsResponse(first);
+            Counters firstCounters = new Counters();
+            firstCounters.inc("foo.foo", 1);
+            firstCounters.inc("foo.bar.baz", 1);
+            firstNode.setStats(firstCounters);
+            nodes.add(firstNode);
+
+            DiscoveryNode second = new DiscoveryNode("second", buildNewFakeTransportAddress(), Version.CURRENT);
+            EqlStatsResponse.NodeStatsResponse secondNode = new EqlStatsResponse.NodeStatsResponse(second);
+            Counters secondCounters = new Counters();
+            secondCounters.inc("spam", 1);
+            secondCounters.inc("foo.bar.baz", 4);
+            secondNode.setStats(secondCounters);
+            nodes.add(secondNode);
+
+            listener.onResponse(new EqlStatsResponse(new ClusterName("whatever"), nodes, Collections.emptyList()));
+            return null;
+        }).when(client).execute(eq(EqlStatsAction.INSTANCE), any(), any());
+        ClusterService clusterService = mock(ClusterService.class);
+        final DiscoveryNode mockNode = mock(DiscoveryNode.class);
+        when(mockNode.getId()).thenReturn("mocknode");
+        when(clusterService.localNode()).thenReturn(mockNode);
+
+        var usageAction = new EqlUsageTransportAction(mock(TransportService.class), clusterService, null,
+            mock(ActionFilters.class), null, Settings.builder().put("xpack.eql.enabled", true).build(), licenseState, client);
+        PlainActionFuture<XPackUsageFeatureResponse> future = new PlainActionFuture<>();
+        usageAction.masterOperation(mock(Task.class), null, null, future);
+        EqlFeatureSetUsage eqlUsage = (EqlFeatureSetUsage) future.get().getUsage();
+        
+        long fooBarBaz = ObjectPath.eval("foo.bar.baz", eqlUsage.stats());
+        long fooFoo = ObjectPath.eval("foo.foo", eqlUsage.stats());
+        long spam = ObjectPath.eval("spam", eqlUsage.stats());
+        
+        assertThat(eqlUsage.stats().keySet(), containsInAnyOrder("foo", "spam"));
+        assertThat(fooBarBaz, is(5L));
+        assertThat(fooFoo, is(1L));
+        assertThat(spam, is(1L));
+    }
+}