瀏覽代碼

Rest HL client: Add watcher stats API (#35185)

Relates to #29827
Igor Motov 7 年之前
父節點
當前提交
a76ac5729d
共有 16 個文件被更改,包括 1092 次插入5 次删除
  1. 29 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java
  2. 23 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java
  3. 50 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/ExecutionPhase.java
  4. 91 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/QueuedWatch.java
  5. 123 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchExecutionSnapshot.java
  6. 54 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherMetaData.java
  7. 54 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherState.java
  8. 53 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherStatsRequest.java
  9. 229 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherStatsResponse.java
  10. 49 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/NodesResponseHeaderTestUtils.java
  11. 26 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java
  12. 40 4
      client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java
  13. 49 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java
  14. 188 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatcherStatsResponseTests.java
  15. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc
  16. 32 0
      docs/java-rest/high-level/watcher/watcher-stats.asciidoc

+ 29 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherClient.java

@@ -32,6 +32,8 @@ import org.elasticsearch.client.watcher.DeleteWatchRequest;
 import org.elasticsearch.client.watcher.DeleteWatchResponse;
 import org.elasticsearch.client.watcher.PutWatchRequest;
 import org.elasticsearch.client.watcher.PutWatchResponse;
+import org.elasticsearch.client.watcher.WatcherStatsRequest;
+import org.elasticsearch.client.watcher.WatcherStatsResponse;
 
 import java.io.IOException;
 
@@ -237,4 +239,31 @@ public final class WatcherClient {
             ActivateWatchResponse::fromXContent, listener, singleton(404));
     }
 
+    /**
+     * Get the watcher stats
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stats.html">
+     * the docs</a> for more.
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public WatcherStatsResponse watcherStats(WatcherStatsRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, WatcherRequestConverters::watcherStats, options,
+            WatcherStatsResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously get the watcher stats
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-api-stats.html">
+     * the docs</a> for more.
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     */
+    public void watcherStatsAsync(WatcherStatsRequest request, RequestOptions options, ActionListener<WatcherStatsResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request, WatcherRequestConverters::watcherStats, options,
+            WatcherStatsResponse::fromXContent, listener, emptySet());
+    }
+
 }

+ 23 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.client;
 
 import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.entity.ByteArrayEntity;
@@ -29,6 +30,7 @@ import org.elasticsearch.client.watcher.ActivateWatchRequest;
 import org.elasticsearch.client.watcher.AckWatchRequest;
 import org.elasticsearch.client.watcher.StartWatchServiceRequest;
 import org.elasticsearch.client.watcher.StopWatchServiceRequest;
+import org.elasticsearch.client.watcher.WatcherStatsRequest;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.client.watcher.DeleteWatchRequest;
 import org.elasticsearch.client.watcher.PutWatchRequest;
@@ -115,4 +117,25 @@ final class WatcherRequestConverters {
         Request request = new Request(HttpPut.METHOD_NAME, endpoint);
         return request;
     }
+
+    static Request watcherStats(WatcherStatsRequest watcherStatsRequest) {
+        RequestConverters.EndpointBuilder builder = new RequestConverters.EndpointBuilder().addPathPartAsIs("_xpack", "watcher", "stats");
+        String endpoint = builder.build();
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+        RequestConverters.Params parameters = new RequestConverters.Params(request);
+        StringBuilder metric = new StringBuilder();
+        if (watcherStatsRequest.includeCurrentWatches()) {
+            metric.append("current_watches");
+        }
+        if (watcherStatsRequest.includeQueuedWatches()) {
+            if (metric.length() > 0) {
+                metric.append(",");
+            }
+            metric.append("queued_watches");
+        }
+        if (metric.length() > 0) {
+            parameters.putParam("metric", metric.toString());
+        }
+        return request;
+    }
 }

+ 50 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/ExecutionPhase.java

@@ -0,0 +1,50 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+public enum ExecutionPhase {
+
+    // awaiting execution of the watch
+    AWAITS_EXECUTION(false),
+    // initial phase, watch execution has started, but the input is not yet processed
+    STARTED(false),
+    // input is being executed
+    INPUT(false),
+    // condition phase is being executed
+    CONDITION(false),
+    // transform phase (optional, depends if a global transform was configured in the watch)
+    WATCH_TRANSFORM(false),
+    // actions phase, all actions, including specific action transforms
+    ACTIONS(false),
+    // missing watch, failed execution of input/condition/transform,
+    ABORTED(true),
+    // successful run
+    FINISHED(true);
+
+    private final boolean sealed;
+
+    ExecutionPhase(boolean sealed) {
+        this.sealed = sealed;
+    }
+
+    public boolean sealed() {
+        return sealed;
+    }
+}

+ 91 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/QueuedWatch.java

@@ -0,0 +1,91 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.joda.time.DateTime;
+
+import java.util.Objects;
+
+public class QueuedWatch {
+
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<QueuedWatch, Void> PARSER =
+        new ConstructingObjectParser<>("watcher_stats_node", true, (args, c) -> new QueuedWatch(
+            (String) args[0],
+            (String) args[1],
+            DateTime.parse((String) args[2]),
+            DateTime.parse((String) args[3])
+        ));
+
+    static {
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("watch_id"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("watch_record_id"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("triggered_time"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("execution_time"));
+    }
+
+
+    private final String watchId;
+    private final String watchRecordId;
+    private final DateTime triggeredTime;
+    private final DateTime executionTime;
+
+    public QueuedWatch(String watchId, String watchRecordId, DateTime triggeredTime, DateTime executionTime) {
+        this.watchId = watchId;
+        this.watchRecordId = watchRecordId;
+        this.triggeredTime = triggeredTime;
+        this.executionTime = executionTime;
+    }
+
+    public String getWatchId() {
+        return watchId;
+    }
+
+    public String getWatchRecordId() {
+        return watchRecordId;
+    }
+
+    public DateTime getTriggeredTime() {
+        return triggeredTime;
+    }
+
+    public DateTime getExecutionTime() {
+        return executionTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        QueuedWatch that = (QueuedWatch) o;
+        return Objects.equals(watchId, that.watchId) &&
+            Objects.equals(watchRecordId, that.watchRecordId) &&
+            Objects.equals(triggeredTime, that.triggeredTime) &&
+            Objects.equals(executionTime, that.executionTime);
+    }
+
+    @Override
+    public int hashCode() {
+
+        return Objects.hash(watchId, watchRecordId, triggeredTime, executionTime);
+    }
+}

+ 123 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatchExecutionSnapshot.java

@@ -0,0 +1,123 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.joda.time.DateTime;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+public class WatchExecutionSnapshot {
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<WatchExecutionSnapshot, Void> PARSER =
+        new ConstructingObjectParser<>("watcher_stats_node", true, (args, c) -> new WatchExecutionSnapshot(
+            (String) args[0],
+            (String) args[1],
+            DateTime.parse((String)  args[2]),
+            DateTime.parse((String)  args[3]),
+            ExecutionPhase.valueOf(((String) args[4]).toUpperCase(Locale.ROOT)),
+            args[5] == null ? null : ((List<String>) args[5]).toArray(new String[0]),
+            args[6] == null ? null : ((List<String>) args[6]).toArray(new String[0])
+        ));
+
+    static {
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("watch_id"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("watch_record_id"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("triggered_time"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("execution_time"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("execution_phase"));
+        PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("executed_actions"));
+        PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("stack_trace"));
+    }
+
+    private final String watchId;
+    private final String watchRecordId;
+    private final DateTime triggeredTime;
+    private final DateTime executionTime;
+    private final ExecutionPhase phase;
+    private final String[] executedActions;
+    private final String[] executionStackTrace;
+
+    public WatchExecutionSnapshot(String watchId, String watchRecordId, DateTime triggeredTime, DateTime executionTime,
+                                  ExecutionPhase phase, String[] executedActions, String[] executionStackTrace) {
+        this.watchId = watchId;
+        this.watchRecordId = watchRecordId;
+        this.triggeredTime = triggeredTime;
+        this.executionTime = executionTime;
+        this.phase = phase;
+        this.executedActions = executedActions;
+        this.executionStackTrace = executionStackTrace;
+    }
+
+    public String getWatchId() {
+        return watchId;
+    }
+
+    public String getWatchRecordId() {
+        return watchRecordId;
+    }
+
+    public DateTime getTriggeredTime() {
+        return triggeredTime;
+    }
+
+    public DateTime getExecutionTime() {
+        return executionTime;
+    }
+
+    public ExecutionPhase getPhase() {
+        return phase;
+    }
+
+    public String[] getExecutedActions() {
+        return executedActions;
+    }
+
+    public String[] getExecutionStackTrace() {
+        return executionStackTrace;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WatchExecutionSnapshot that = (WatchExecutionSnapshot) o;
+        return Objects.equals(watchId, that.watchId) &&
+            Objects.equals(watchRecordId, that.watchRecordId) &&
+            Objects.equals(triggeredTime, that.triggeredTime) &&
+            Objects.equals(executionTime, that.executionTime) &&
+            phase == that.phase &&
+            Arrays.equals(executedActions, that.executedActions) &&
+            Arrays.equals(executionStackTrace, that.executionStackTrace);
+    }
+
+    @Override
+    public int hashCode() {
+
+        int result = Objects.hash(watchId, watchRecordId, triggeredTime, executionTime, phase);
+        result = 31 * result + Arrays.hashCode(executedActions);
+        result = 31 * result + Arrays.hashCode(executionStackTrace);
+        return result;
+    }
+}

+ 54 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherMetaData.java

@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import java.util.Objects;
+
+public class WatcherMetaData {
+
+    private final boolean manuallyStopped;
+
+    public WatcherMetaData(boolean manuallyStopped) {
+        this.manuallyStopped = manuallyStopped;
+    }
+
+    public boolean manuallyStopped() {
+        return manuallyStopped;
+    }
+    @Override
+    public String toString() {
+        return "manuallyStopped["+ manuallyStopped +"]";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        WatcherMetaData action = (WatcherMetaData) o;
+
+        return manuallyStopped == action.manuallyStopped;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(manuallyStopped);
+    }
+}

+ 54 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherState.java

@@ -0,0 +1,54 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.watcher;
+
+public enum WatcherState {
+
+    /**
+     * The watcher plugin is not running and not functional.
+     */
+    STOPPED(0),
+
+    /**
+     * The watcher plugin is performing the necessary operations to get into a started state.
+     */
+    STARTING(1),
+
+    /**
+     * The watcher plugin is running and completely functional.
+     */
+    STARTED(2),
+
+    /**
+     * The watcher plugin is shutting down and not functional.
+     */
+    STOPPING(3);
+
+    private final byte id;
+
+    WatcherState(int id) {
+        this.id = (byte) id;
+    }
+
+    public byte getId() {
+        return id;
+    }
+
+}
+

+ 53 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherStatsRequest.java

@@ -0,0 +1,53 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import org.elasticsearch.client.Validatable;
+
+/**
+ * A request to explicitly acknowledge a watch.
+ */
+public class WatcherStatsRequest implements Validatable {
+
+    private final boolean includeCurrentWatches;
+    private final boolean includeQueuedWatches;
+
+    public WatcherStatsRequest( ) {
+        this(true, true);
+    }
+
+    public WatcherStatsRequest(boolean includeCurrentWatches, boolean includeQueuedWatches) {
+        this.includeCurrentWatches = includeCurrentWatches;
+        this.includeQueuedWatches = includeQueuedWatches;
+    }
+
+    public boolean includeCurrentWatches() {
+        return includeCurrentWatches;
+    }
+
+    public boolean includeQueuedWatches() {
+        return includeQueuedWatches;
+    }
+
+    @Override
+    public String toString() {
+        return "stats [current=" + includeCurrentWatches + ", " + "queued=" + includeQueuedWatches + "]";
+    }
+}

+ 229 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/WatcherStatsResponse.java

@@ -0,0 +1,229 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import org.elasticsearch.client.NodesResponseHeader;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The response from an 'ack watch' request.
+ */
+public class WatcherStatsResponse {
+
+    private final List<Node> nodes;
+    private final NodesResponseHeader header;
+    private final String clusterName;
+
+    private final WatcherMetaData watcherMetaData;
+
+    public WatcherStatsResponse(NodesResponseHeader header, String clusterName, WatcherMetaData watcherMetaData, List<Node> nodes) {
+        this.nodes = nodes;
+        this.header = header;
+        this.clusterName = clusterName;
+        this.watcherMetaData = watcherMetaData;
+    }
+
+    /**
+     * @return the status of the requested watch. If an action was
+     * successfully acknowledged, this will be reflected in its status.
+     */
+    public WatcherMetaData getWatcherMetaData() {
+        return watcherMetaData;
+    }
+
+    /**
+     * returns a list of nodes that returned stats
+     */
+    public List<Node> getNodes() {
+        return nodes;
+    }
+
+    /**
+     * Gets information about the number of total, successful and failed nodes the request was run on.
+     * Also includes exceptions if relevant.
+     */
+    public NodesResponseHeader getHeader() {
+        return header;
+    }
+
+    /**
+     * Get the cluster name associated with all of the nodes.
+     *
+     * @return Never {@code null}.
+     */
+    public String getClusterName() {
+        return clusterName;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static ConstructingObjectParser<WatcherStatsResponse, Void> PARSER =
+        new ConstructingObjectParser<>("watcher_stats_response", true,
+            a -> new WatcherStatsResponse((NodesResponseHeader) a[0], (String) a[1], new WatcherMetaData((boolean) a[2]),
+                (List<Node>) a[3]));
+
+    static {
+        PARSER.declareObject(ConstructingObjectParser.constructorArg(), NodesResponseHeader::fromXContent, new ParseField("_nodes"));
+        PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("cluster_name"));
+        PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), new ParseField("manually_stopped"));
+        PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), (p, c) -> Node.PARSER.apply(p, null),
+            new ParseField("stats"));
+    }
+
+    public static WatcherStatsResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        WatcherStatsResponse that = (WatcherStatsResponse) o;
+        return Objects.equals(nodes, that.nodes) &&
+            Objects.equals(header, that.header) &&
+            Objects.equals(clusterName, that.clusterName) &&
+            Objects.equals(watcherMetaData, that.watcherMetaData);
+    }
+
+    @Override
+    public int hashCode() {
+
+        return Objects.hash(nodes, header, clusterName, watcherMetaData);
+    }
+
+    public static class Node {
+        @SuppressWarnings("unchecked")
+        public static final ConstructingObjectParser<Node, Void> PARSER =
+            new ConstructingObjectParser<>("watcher_stats_node", true, (args, c) -> new Node(
+                (String) args[0],
+                WatcherState.valueOf(((String) args[1]).toUpperCase(Locale.ROOT)),
+                (long) args[2],
+                ((Tuple<Long, Long>) args[3]).v1(),
+                ((Tuple<Long, Long>) args[3]).v2(),
+                (List<WatchExecutionSnapshot>) args[4],
+                (List<QueuedWatch>) args[5],
+                (Map<String, Object>) args[6]
+
+            ));
+
+        private static final ConstructingObjectParser<Tuple<Long, Long>, Void> THREAD_POOL_PARSER =
+            new ConstructingObjectParser<>("execution_thread_pool", true, (args, id) -> new Tuple<>((Long) args[0], (Long) args[1]));
+
+        static {
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("node_id"));
+            PARSER.declareString(ConstructingObjectParser.constructorArg(), new ParseField("watcher_state"));
+            PARSER.declareLong(ConstructingObjectParser.constructorArg(), new ParseField("watch_count"));
+            PARSER.declareObject(ConstructingObjectParser.constructorArg(), THREAD_POOL_PARSER::apply,
+                new ParseField("execution_thread_pool"));
+            PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), WatchExecutionSnapshot.PARSER,
+                new ParseField("current_watches"));
+            PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), QueuedWatch.PARSER,
+                new ParseField("queued_watches"));
+            PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), new ParseField("stats"));
+
+            THREAD_POOL_PARSER.declareLong(ConstructingObjectParser.constructorArg(), new ParseField("queue_size"));
+            THREAD_POOL_PARSER.declareLong(ConstructingObjectParser.constructorArg(), new ParseField("max_size"));
+        }
+
+        private final String nodeId;
+
+        private WatcherState watcherState;
+        private long watchesCount;
+        private long threadPoolQueueSize;
+        private long threadPoolMaxSize;
+        private List<WatchExecutionSnapshot> snapshots;
+        private List<QueuedWatch> queuedWatches;
+        private Map<String, Object> stats;
+
+
+        public Node(String nodeId, WatcherState watcherState, long watchesCount, long threadPoolQueueSize, long threadPoolMaxSize,
+                    List<WatchExecutionSnapshot> snapshots, List<QueuedWatch> queuedWatches, Map<String, Object> stats) {
+            this.nodeId = nodeId;
+            this.watcherState = watcherState;
+            this.watchesCount = watchesCount;
+            this.threadPoolQueueSize = threadPoolQueueSize;
+            this.threadPoolMaxSize = threadPoolMaxSize;
+            this.snapshots = snapshots;
+            this.queuedWatches = queuedWatches;
+            this.stats = stats;
+        }
+
+        public String getNodeId() {
+            return nodeId;
+        }
+
+        public long getWatchesCount() {
+            return watchesCount;
+        }
+
+        public WatcherState getWatcherState() {
+            return watcherState;
+        }
+
+        public long getThreadPoolQueueSize() {
+            return threadPoolQueueSize;
+        }
+
+        public long getThreadPoolMaxSize() {
+            return threadPoolMaxSize;
+        }
+
+        public List<WatchExecutionSnapshot> getSnapshots() {
+            return snapshots;
+        }
+
+        public List<QueuedWatch> getQueuedWatches() {
+            return queuedWatches;
+        }
+
+        public Map<String, Object> getStats() {
+            return stats;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Node node = (Node) o;
+            return watchesCount == node.watchesCount &&
+                threadPoolQueueSize == node.threadPoolQueueSize &&
+                threadPoolMaxSize == node.threadPoolMaxSize &&
+                Objects.equals(nodeId, node.nodeId) &&
+                watcherState == node.watcherState &&
+                Objects.equals(snapshots, node.snapshots) &&
+                Objects.equals(queuedWatches, node.queuedWatches) &&
+                Objects.equals(stats, node.stats);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(nodeId, watcherState, watchesCount, threadPoolQueueSize, threadPoolMaxSize, snapshots, queuedWatches,
+                stats);
+        }
+    }
+}

+ 49 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/NodesResponseHeaderTestUtils.java

@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client;
+
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public class NodesResponseHeaderTestUtils {
+
+    public static void toXContent(NodesResponseHeader header, String clusterName, XContentBuilder builder) throws IOException {
+        builder.startObject("_nodes");
+        builder.field("total", header.getTotal());
+        builder.field("successful", header.getSuccessful());
+        builder.field("failed", header.getFailed());
+
+        if (header.getFailures().isEmpty() == false) {
+            builder.startArray("failures");
+            for (ElasticsearchException failure : header.getFailures()) {
+                builder.startObject();
+                failure.toXContent(builder, ToXContent.EMPTY_PARAMS);
+                builder.endObject();
+            }
+            builder.endArray();
+        }
+
+        builder.endObject();
+        builder.field("cluster_name", clusterName);
+    }
+
+}

+ 26 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherIT.java

@@ -32,6 +32,9 @@ import org.elasticsearch.client.watcher.ActivateWatchRequest;
 import org.elasticsearch.client.watcher.ActivateWatchResponse;
 import org.elasticsearch.client.watcher.StartWatchServiceRequest;
 import org.elasticsearch.client.watcher.StopWatchServiceRequest;
+import org.elasticsearch.client.watcher.WatcherState;
+import org.elasticsearch.client.watcher.WatcherStatsRequest;
+import org.elasticsearch.client.watcher.WatcherStatsResponse;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.xcontent.XContentType;
@@ -41,8 +44,10 @@ import org.elasticsearch.client.watcher.PutWatchRequest;
 import org.elasticsearch.client.watcher.PutWatchResponse;
 import org.elasticsearch.rest.RestStatus;
 
+import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.not;
 
 public class WatcherIT extends ESRestHighLevelClientTestCase {
 
@@ -50,12 +55,22 @@ public class WatcherIT extends ESRestHighLevelClientTestCase {
         AcknowledgedResponse response =
                 highLevelClient().watcher().startWatchService(new StartWatchServiceRequest(), RequestOptions.DEFAULT);
         assertTrue(response.isAcknowledged());
+
+        WatcherStatsResponse stats = highLevelClient().watcher().watcherStats(new WatcherStatsRequest(), RequestOptions.DEFAULT);
+        assertFalse(stats.getWatcherMetaData().manuallyStopped());
+        assertThat(stats.getNodes(), not(empty()));
+        for(WatcherStatsResponse.Node node : stats.getNodes()) {
+            assertEquals(WatcherState.STARTED, node.getWatcherState());
+        }
     }
 
     public void testStopWatchService() throws Exception {
         AcknowledgedResponse response =
                 highLevelClient().watcher().stopWatchService(new StopWatchServiceRequest(), RequestOptions.DEFAULT);
         assertTrue(response.isAcknowledged());
+
+        WatcherStatsResponse stats = highLevelClient().watcher().watcherStats(new WatcherStatsRequest(), RequestOptions.DEFAULT);
+        assertTrue(stats.getWatcherMetaData().manuallyStopped());
     }
 
     public void testPutWatch() throws Exception {
@@ -169,4 +184,15 @@ public class WatcherIT extends ESRestHighLevelClientTestCase {
             highLevelClient().watcher().activateWatch(new ActivateWatchRequest(watchId), RequestOptions.DEFAULT));
         assertEquals(RestStatus.NOT_FOUND, exception.status());
     }
+
+    public void testWatcherStatsMetrics() throws Exception {
+        boolean includeCurrent = randomBoolean();
+        boolean includeQueued = randomBoolean();
+        WatcherStatsRequest request = new WatcherStatsRequest(includeCurrent, includeQueued);
+
+        WatcherStatsResponse stats = highLevelClient().watcher().watcherStats(request, RequestOptions.DEFAULT);
+        assertThat(stats.getNodes(), not(empty()));
+        assertEquals(includeCurrent, stats.getNodes().get(0).getSnapshots() != null);
+        assertEquals(includeQueued, stats.getNodes().get(0).getQueuedWatches() != null);
+    }
 }

+ 40 - 4
client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java

@@ -20,25 +20,34 @@
 package org.elasticsearch.client;
 
 import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
-import org.elasticsearch.client.watcher.DeactivateWatchRequest;
-import org.elasticsearch.client.watcher.ActivateWatchRequest;
 import org.elasticsearch.client.watcher.AckWatchRequest;
+import org.elasticsearch.client.watcher.ActivateWatchRequest;
+import org.elasticsearch.client.watcher.DeactivateWatchRequest;
+import org.elasticsearch.client.watcher.DeleteWatchRequest;
+import org.elasticsearch.client.watcher.PutWatchRequest;
 import org.elasticsearch.client.watcher.StartWatchServiceRequest;
 import org.elasticsearch.client.watcher.StopWatchServiceRequest;
+import org.elasticsearch.client.watcher.WatcherStatsRequest;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.client.watcher.DeleteWatchRequest;
-import org.elasticsearch.client.watcher.PutWatchRequest;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.ByteArrayOutputStream;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.StringJoiner;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.hasKey;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
 
 public class WatcherRequestConvertersTests extends ESTestCase {
@@ -130,4 +139,31 @@ public class WatcherRequestConvertersTests extends ESTestCase {
         assertEquals("/_xpack/watcher/watch/" + watchId + "/_activate", request.getEndpoint());
         assertThat(request.getEntity(), nullValue());
     }
+
+    public void testWatcherStatsRequest() {
+        boolean includeCurrent = randomBoolean();
+        boolean includeQueued = randomBoolean();
+
+        WatcherStatsRequest watcherStatsRequest = new WatcherStatsRequest(includeCurrent, includeQueued);
+
+        Request request = WatcherRequestConverters.watcherStats(watcherStatsRequest);
+        assertThat(request.getEndpoint(), equalTo("/_xpack/watcher/stats"));
+        assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME));
+        if (includeCurrent || includeQueued) {
+            assertThat(request.getParameters(), hasKey("metric"));
+            Set<String> metric = Strings.tokenizeByCommaToSet(request.getParameters().get("metric"));
+            assertThat(metric, hasSize((includeCurrent?1:0) + (includeQueued?1:0)));
+            Set<String> expectedMetric = new HashSet<>();
+            if (includeCurrent) {
+                expectedMetric.add("current_watches");
+            }
+            if (includeQueued) {
+                expectedMetric.add("queued_watches");
+            }
+            assertThat(metric, equalTo(expectedMetric));
+        } else {
+            assertThat(request.getParameters(), not(hasKey("metric")));
+        }
+        assertThat(request.getEntity(), nullValue());
+    }
 }

+ 49 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/WatcherDocumentationIT.java

@@ -37,6 +37,8 @@ import org.elasticsearch.client.watcher.DeactivateWatchResponse;
 import org.elasticsearch.client.watcher.StartWatchServiceRequest;
 import org.elasticsearch.client.watcher.StopWatchServiceRequest;
 import org.elasticsearch.client.watcher.WatchStatus;
+import org.elasticsearch.client.watcher.WatcherStatsRequest;
+import org.elasticsearch.client.watcher.WatcherStatsResponse;
 import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.xcontent.XContentType;
@@ -46,6 +48,7 @@ import org.elasticsearch.client.watcher.PutWatchRequest;
 import org.elasticsearch.client.watcher.PutWatchResponse;
 import org.elasticsearch.rest.RestStatus;
 
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -100,7 +103,7 @@ public class WatcherDocumentationIT extends ESRestHighLevelClientTestCase {
                 }
             };
             // end::start-watch-service-execute-listener
-            
+
             CountDownLatch latch = new CountDownLatch(1);
             listener = new LatchedActionListener<>(listener, latch);
 
@@ -408,4 +411,49 @@ public class WatcherDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testWatcherStats() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            //tag::watcher-stats-request
+            WatcherStatsRequest request = new WatcherStatsRequest(true, true);
+            //end::watcher-stats-request
+
+            //tag::watcher-stats-execute
+            WatcherStatsResponse response = client.watcher().watcherStats(request, RequestOptions.DEFAULT);
+            //end::watcher-stats-execute
+
+            //tag::watcher-stats-response
+            List<WatcherStatsResponse.Node> nodes = response.getNodes(); // <1>
+            //end::watcher-stats-response
+        }
+
+        {
+            WatcherStatsRequest request = new WatcherStatsRequest();
+
+            // tag::watcher-stats-execute-listener
+            ActionListener<WatcherStatsResponse> listener = new ActionListener<WatcherStatsResponse>() {
+                @Override
+                public void onResponse(WatcherStatsResponse response) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::watcher-stats-execute-listener
+
+            CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::watcher-stats-execute-async
+            client.watcher().watcherStatsAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::watcher-stats-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+    }
+
 }

+ 188 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/watcher/WatcherStatsResponseTests.java

@@ -0,0 +1,188 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.watcher;
+
+import org.elasticsearch.client.NodesResponseHeader;
+import org.elasticsearch.client.NodesResponseHeaderTestUtils;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.test.ESTestCase;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
+
+public class WatcherStatsResponseTests extends ESTestCase {
+
+    public void testFromXContent() throws IOException {
+        xContentTester(
+            this::createParser,
+            this::createTestInstance,
+            this::toXContent,
+            WatcherStatsResponse::fromXContent)
+            .supportsUnknownFields(true)
+            .randomFieldsExcludeFilter(field -> field.endsWith("stats"))
+            .test();
+    }
+
+    private void toXContent(WatcherStatsResponse response, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        NodesResponseHeaderTestUtils.toXContent(response.getHeader(), response.getClusterName(), builder);
+        toXContent(response.getWatcherMetaData(), builder);
+        builder.startArray("stats");
+        for (WatcherStatsResponse.Node node : response.getNodes()) {
+            toXContent(node, builder);
+        }
+        builder.endArray();
+        builder.endObject();
+    }
+
+    private void toXContent(WatcherMetaData metaData, XContentBuilder builder) throws IOException {
+        builder.field("manually_stopped", metaData.manuallyStopped());
+    }
+
+    private void toXContent(WatcherStatsResponse.Node node, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        builder.field("node_id", node.getNodeId());
+        builder.field("watcher_state", node.getWatcherState().toString().toLowerCase(Locale.ROOT));
+        builder.field("watch_count", node.getWatchesCount());
+        builder.startObject("execution_thread_pool");
+        builder.field("queue_size", node.getThreadPoolQueueSize());
+        builder.field("max_size", node.getThreadPoolMaxSize());
+        builder.endObject();
+
+        if (node.getSnapshots() != null) {
+            builder.startArray("current_watches");
+            for (WatchExecutionSnapshot snapshot : node.getSnapshots()) {
+                toXContent(snapshot, builder);
+            }
+            builder.endArray();
+        }
+        if (node.getQueuedWatches() != null) {
+            builder.startArray("queued_watches");
+            for (QueuedWatch queuedWatch : node.getQueuedWatches()) {
+                toXContent(queuedWatch, builder);
+            }
+            builder.endArray();
+        }
+        if (node.getStats() != null) {
+            builder.field("stats", node.getStats());
+        }
+        builder.endObject();
+    }
+
+    private void toXContent(WatchExecutionSnapshot snapshot, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        builder.field("watch_id", snapshot.getWatchId());
+        builder.field("watch_record_id", snapshot.getWatchRecordId());
+        builder.timeField("triggered_time", snapshot.getTriggeredTime());
+        builder.timeField("execution_time", snapshot.getExecutionTime());
+        builder.field("execution_phase", snapshot.getPhase());
+        if (snapshot.getExecutedActions() != null) {
+            builder.startArray("executed_actions");
+            for (String executedAction : snapshot.getExecutedActions()) {
+                builder.value(executedAction);
+            }
+            builder.endArray();
+        }
+        if (snapshot.getExecutionStackTrace() != null) {
+            builder.startArray("stack_trace");
+            for (String element : snapshot.getExecutionStackTrace()) {
+                builder.value(element);
+            }
+            builder.endArray();
+        }
+        builder.endObject();
+    }
+
+    private void toXContent(QueuedWatch queuedWatch, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        builder.field("watch_id", queuedWatch.getWatchId());
+        builder.field("watch_record_id", queuedWatch.getWatchRecordId());
+        builder.timeField("triggered_time", queuedWatch.getTriggeredTime());
+        builder.timeField("execution_time", queuedWatch.getExecutionTime());
+        builder.endObject();
+    }
+
+    protected WatcherStatsResponse createTestInstance() {
+        int nodeCount = randomInt(10);
+        List<WatcherStatsResponse.Node> nodes = new ArrayList<>(nodeCount);
+        for (int i = 0; i < nodeCount; i++) {
+            List<WatchExecutionSnapshot> snapshots = null;
+            if (randomBoolean()) {
+                int snapshotCount = randomInt(10);
+                snapshots = new ArrayList<>(snapshotCount);
+
+                for (int j = 0; j < snapshotCount; j++) {
+                    String[] actions = null;
+                    if (randomBoolean()) {
+                        actions = new String[randomInt(10)];
+                        for (int k = 0; k < actions.length; k++) {
+                            actions[k] = randomAlphaOfLength(10);
+                        }
+                    }
+                    String[] stackTrace = null;
+                    if (randomBoolean()) {
+                        stackTrace = new String[randomInt(10)];
+                        for (int k = 0; k < stackTrace.length; k++) {
+                            stackTrace[k] = randomAlphaOfLength(10);
+                        }
+                    }
+                    snapshots.add(new WatchExecutionSnapshot(randomAlphaOfLength(10), randomAlphaOfLength(10),
+                        new DateTime(randomInt(), DateTimeZone.UTC), new DateTime(randomInt(), DateTimeZone.UTC),
+                        randomFrom(ExecutionPhase.values()), actions, stackTrace));
+                }
+            }
+
+            List<QueuedWatch> queuedWatches = null;
+            if(randomBoolean()) {
+                int queuedWatchCount = randomInt(10);
+                queuedWatches = new ArrayList<>(queuedWatchCount);
+                for (int j=0; j<queuedWatchCount; j++) {
+                    queuedWatches.add(new QueuedWatch(randomAlphaOfLength(10), randomAlphaOfLength(10),
+                        new DateTime(randomInt(), DateTimeZone.UTC), new DateTime(randomInt(), DateTimeZone.UTC)));
+                }
+            }
+
+            Map<String, Object> stats = null;
+            if (randomBoolean()) {
+                int statsCount = randomInt(10);
+                stats = new HashMap<>(statsCount);
+                for (int j=0; j<statsCount; j++) {
+                    stats.put(randomAlphaOfLength(10), randomNonNegativeLong());
+                }
+            }
+
+            nodes.add(new WatcherStatsResponse.Node(randomAlphaOfLength(10), randomFrom(WatcherState.values()), randomNonNegativeLong(),
+                randomNonNegativeLong(), randomNonNegativeLong(), snapshots, queuedWatches, stats));
+        }
+        NodesResponseHeader nodesResponseHeader = new NodesResponseHeader(randomInt(10), randomInt(10),
+            randomInt(10), Collections.emptyList());
+        WatcherMetaData watcherMetaData = new WatcherMetaData(randomBoolean());
+        return new WatcherStatsResponse(nodesResponseHeader, randomAlphaOfLength(10), watcherMetaData, nodes);
+    }
+}

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -373,6 +373,7 @@ The Java High Level REST Client supports the following Watcher APIs:
 * <<java-rest-high-watcher-deactivate-watch>>
 * <<{upid}-ack-watch>>
 * <<{upid}-activate-watch>>
+* <<{upid}-watcher-stats>>
 
 include::watcher/start-watch-service.asciidoc[]
 include::watcher/stop-watch-service.asciidoc[]
@@ -381,6 +382,7 @@ include::watcher/delete-watch.asciidoc[]
 include::watcher/ack-watch.asciidoc[]
 include::watcher/deactivate-watch.asciidoc[]
 include::watcher/activate-watch.asciidoc[]
+include::watcher/watcher-stats.asciidoc[]
 
 == Graph APIs
 

+ 32 - 0
docs/java-rest/high-level/watcher/watcher-stats.asciidoc

@@ -0,0 +1,32 @@
+--
+:api: watcher-stats
+:request: WatcherStatsRequest
+:response: WatcherStatsResponse
+--
+[id="{upid}-{api}"]
+=== Watcher Stats API
+
+[id="{upid}-{api}-request"]
+==== Execution
+
+{ref}/watcher-api-stats.html[Watcher Stats] returns the current {watcher} metrics.
+Submit the following request to get the stats:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+[id="{upid}-{api}-response"]
+==== Response
+
+The returned `AcknowledgeResponse` contains a value on whether or not the request
+was received:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> A boolean value of `true` if successfully received, `false` otherwise.
+
+include::../execution.asciidoc[]