diff --git a/docs/reference/cluster.asciidoc b/docs/reference/cluster.asciidoc index 6c7e4d7eb2874..2557676072345 100644 --- a/docs/reference/cluster.asciidoc +++ b/docs/reference/cluster.asciidoc @@ -33,6 +33,8 @@ include::cluster/health.asciidoc[] include::cluster/state.asciidoc[] +include::cluster/stats.asciidoc[] + include::cluster/pending.asciidoc[] include::cluster/reroute.asciidoc[] diff --git a/docs/reference/cluster/stats.asciidoc b/docs/reference/cluster/stats.asciidoc new file mode 100644 index 0000000000000..7ead927a62aee --- /dev/null +++ b/docs/reference/cluster/stats.asciidoc @@ -0,0 +1,165 @@ +[[cluster-stats]] +== Cluster Stats + +added[0.90.8] + +The Cluster Stats API allows to retrieve statistics from a cluster wide perspective. +The API returns basic index metrics (shard numbers, store size, memory usage) and +information about the current nodes that form the cluster (number, roles, os, jvm +versions, memory usage, cpu and installed plugins). + +[source,js] +-------------------------------------------------- +curl -XGET 'http://localhost:9200/_cluster/stats?human' +-------------------------------------------------- + +Will return, for example: +[source,js] +-------------------------------------------------- +{ + "cluster_name": "elasticsearch", + "indices": { + "count": 3, + "shards": { + "total": 35, + "primaries": 15, + "replication": 1.333333333333333, + "index": { + "shards": { + "min": 10, + "max": 15, + "avg": 11.66666666666666 + }, + "primaries": { + "min": 5, + "max": 5, + "avg": 5 + }, + "replication": { + "min": 1, + "max": 2, + "avg": 1.3333333333333333 + } + } + }, + "docs": { + "count": 2, + "deleted": 0 + }, + "store": { + "size": "5.6kb", + "size_in_bytes": 5770, + "throttle_time": "0s", + "throttle_time_in_millis": 0 + }, + "fielddata": { + "memory_size": "0b", + "memory_size_in_bytes": 0, + "evictions": 0 + }, + "filter_cache": { + "memory_size": "0b", + "memory_size_in_bytes": 0, + "evictions": 0 + }, + "id_cache": { + "memory_size": "0b", + "memory_size_in_bytes": 0 + }, + "completion": { + "size": "0b", + "size_in_bytes": 0 + }, + "segments": { + "count": 2 + } + }, + "nodes": { + "count": { + "total": 2, + "master_only": 0, + "data_only": 0, + "master_data": 2, + "client": 0 + }, + "versions": [ + "0.90.8" + ], + "os": { + "available_processors": 4, + "mem": { + "total": "8gb", + "total_in_bytes": 8589934592 + }, + "cpu": [ + { + "vendor": "Intel", + "model": "MacBookAir5,2", + "mhz": 2000, + "total_cores": 4, + "total_sockets": 4, + "cores_per_socket": 16, + "cache_size": "256b", + "cache_size_in_bytes": 256, + "count": 1 + } + ] + }, + "process": { + "cpu": { + "percent": 3 + }, + "avg_open_file_descriptors": 218 + }, + "jvm": { + "max_uptime": "24s", + "max_uptime_in_millis": 24054, + "version": [ + { + "version": "1.6.0_45", + "vm_name": "Java HotSpot(TM) 64-Bit Server VM", + "vm_version": "20.45-b01-451", + "vm_vendor": "Apple Inc.", + "count": 2 + } + ], + "mem": { + "heap_used": "38.3mb", + "heap_used_in_bytes": 40237120, + "heap_max": "1.9gb", + "heap_max_in_bytes": 2130051072 + }, + "threads": 89 + }, + "fs": + { + "total": "232.9gb", + "total_in_bytes": 250140434432, + "free": "31.3gb", + "free_in_bytes": 33705881600, + "available": "31.1gb", + "available_in_bytes": 33443737600, + "disk_reads": 21202753, + "disk_writes": 27028840, + "disk_io_op": 48231593, + "disk_read_size": "528gb", + "disk_read_size_in_bytes": 566980806656, + "disk_write_size": "617.9gb", + "disk_write_size_in_bytes": 663525366784, + "disk_io_size": "1145.9gb", + "disk_io_size_in_bytes": 1230506173440 + }, + "plugins": [ + // all plugins installed on nodes + { + "name": "inquisitor", + "description": "", + "url": "/_plugin/inquisitor/", + "jvm": false, + "site": true + } + ] + } +} +-------------------------------------------------- + diff --git a/src/main/java/org/elasticsearch/action/ActionModule.java b/src/main/java/org/elasticsearch/action/ActionModule.java index 4ec9131783f72..0e7b58e1830f9 100644 --- a/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/src/main/java/org/elasticsearch/action/ActionModule.java @@ -54,6 +54,8 @@ import org.elasticsearch.action.admin.cluster.snapshots.restore.TransportRestoreSnapshotAction; import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; import org.elasticsearch.action.admin.cluster.state.TransportClusterStateAction; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; +import org.elasticsearch.action.admin.cluster.stats.TransportClusterStatsAction; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction; import org.elasticsearch.action.admin.cluster.tasks.TransportPendingClusterTasksAction; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction; @@ -199,6 +201,7 @@ protected void configure() { registerAction(NodesRestartAction.INSTANCE, TransportNodesRestartAction.class); registerAction(NodesHotThreadsAction.INSTANCE, TransportNodesHotThreadsAction.class); + registerAction(ClusterStatsAction.INSTANCE, TransportClusterStatsAction.class); registerAction(ClusterStateAction.INSTANCE, TransportClusterStateAction.class); registerAction(ClusterHealthAction.INSTANCE, TransportClusterHealthAction.class); registerAction(ClusterUpdateSettingsAction.INSTANCE, TransportClusterUpdateSettingsAction.class); diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java index d86fc97d00f07..5b705b68ef5a6 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java @@ -29,10 +29,11 @@ public PluginInfo() { /** * Information about plugins - * @param name Its name + * + * @param name Its name * @param description Its description - * @param site true if it's a site plugin - * @param jvm true if it's a jvm plugin + * @param site true if it's a site plugin + * @param jvm true if it's a jvm plugin */ public PluginInfo(String name, String description, boolean site, boolean jvm) { this.name = name; @@ -71,6 +72,7 @@ public boolean isJvm() { /** * We compute the URL for sites: "/_plugin/" + name + "/" + * * @return */ public String getUrl() { @@ -117,4 +119,24 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PluginInfo p = (PluginInfo) o; + + return name.equals(p.getName()); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsAction.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsAction.java new file mode 100644 index 0000000000000..be27cd541018a --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsAction.java @@ -0,0 +1,45 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import org.elasticsearch.action.admin.cluster.ClusterAction; +import org.elasticsearch.client.ClusterAdminClient; + +/** + */ +public class ClusterStatsAction extends ClusterAction { + + public static final ClusterStatsAction INSTANCE = new ClusterStatsAction(); + public static final String NAME = "cluster/stats"; + + private ClusterStatsAction() { + super(NAME); + } + + @Override + public ClusterStatsResponse newResponse() { + return new ClusterStatsResponse(); + } + + @Override + public ClusterStatsRequestBuilder newRequestBuilder(ClusterAdminClient client) { + return new ClusterStatsRequestBuilder(client); + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsIndices.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsIndices.java new file mode 100644 index 0000000000000..d9814e108918c --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsIndices.java @@ -0,0 +1,416 @@ +package org.elasticsearch.action.admin.cluster.stats; +/* + * 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. + */ + + +import com.carrotsearch.hppc.ObjectObjectOpenHashMap; +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; +import org.elasticsearch.action.admin.indices.stats.CommonStats; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; +import org.elasticsearch.index.cache.filter.FilterCacheStats; +import org.elasticsearch.index.cache.id.IdCacheStats; +import org.elasticsearch.index.engine.SegmentsStats; +import org.elasticsearch.index.fielddata.FieldDataStats; +import org.elasticsearch.index.percolator.stats.PercolateStats; +import org.elasticsearch.index.shard.DocsStats; +import org.elasticsearch.index.store.StoreStats; +import org.elasticsearch.search.suggest.completion.CompletionStats; + +import java.io.IOException; + +public class ClusterStatsIndices implements ToXContent, Streamable { + + private int indexCount; + private ShardStats shards; + private DocsStats docs; + private StoreStats store; + private FieldDataStats fieldData; + private FilterCacheStats filterCache; + private IdCacheStats idCache; + private CompletionStats completion; + private SegmentsStats segments; + private PercolateStats peroclate; + + private ClusterStatsIndices() { + } + + public ClusterStatsIndices(ClusterStatsNodeResponse[] nodeResponses) { + ObjectObjectOpenHashMap countsPerIndex = new ObjectObjectOpenHashMap(); + + this.docs = new DocsStats(); + this.store = new StoreStats(); + this.fieldData = new FieldDataStats(); + this.filterCache = new FilterCacheStats(); + this.idCache = new IdCacheStats(); + this.completion = new CompletionStats(); + this.segments = new SegmentsStats(); + this.peroclate = new PercolateStats(); + + for (ClusterStatsNodeResponse r : nodeResponses) { + for (org.elasticsearch.action.admin.indices.stats.ShardStats shardStats : r.shardsStats()) { + ShardStats indexShardStats = countsPerIndex.get(shardStats.getIndex()); + if (indexShardStats == null) { + indexShardStats = new ShardStats(); + countsPerIndex.put(shardStats.getIndex(), indexShardStats); + } + + indexShardStats.total++; + + CommonStats shardCommonStats = shardStats.getStats(); + + if (shardStats.getShardRouting().primary()) { + indexShardStats.primaries++; + docs.add(shardCommonStats.docs); + } + store.add(shardCommonStats.store); + fieldData.add(shardCommonStats.fieldData); + filterCache.add(shardCommonStats.filterCache); + idCache.add(shardCommonStats.idCache); + completion.add(shardCommonStats.completion); + segments.add(shardCommonStats.segments); + peroclate.add(shardCommonStats.percolate); + } + } + + shards = new ShardStats(); + indexCount = countsPerIndex.size(); + for (ObjectObjectCursor indexCountsCursor : countsPerIndex) { + shards.addIndexShardCount(indexCountsCursor.value); + } + } + + public int indexCount() { + return indexCount; + } + + public ShardStats shards() { + return this.shards; + } + + public DocsStats docs() { + return docs; + } + + public StoreStats store() { + return store; + } + + public FieldDataStats fieldData() { + return fieldData; + } + + public FilterCacheStats filterCache() { + return filterCache; + } + + public IdCacheStats idCache() { + return idCache; + } + + public CompletionStats completion() { + return completion; + } + + public SegmentsStats segments() { + return segments; + } + + public PercolateStats percolate() { + return peroclate; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + indexCount = in.readVInt(); + shards = ShardStats.readShardStats(in); + docs = DocsStats.readDocStats(in); + store = StoreStats.readStoreStats(in); + fieldData = FieldDataStats.readFieldDataStats(in); + filterCache = FilterCacheStats.readFilterCacheStats(in); + idCache = IdCacheStats.readIdCacheStats(in); + completion = CompletionStats.readCompletionStats(in); + segments = SegmentsStats.readSegmentsStats(in); + peroclate = PercolateStats.readPercolateStats(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(indexCount); + shards.writeTo(out); + docs.writeTo(out); + store.writeTo(out); + fieldData.writeTo(out); + filterCache.writeTo(out); + idCache.writeTo(out); + completion.writeTo(out); + segments.writeTo(out); + peroclate.writeTo(out); + } + + public static ClusterStatsIndices readIndicesStats(StreamInput in) throws IOException { + ClusterStatsIndices indicesStats = new ClusterStatsIndices(); + indicesStats.readFrom(in); + return indicesStats; + } + + static final class Fields { + static final XContentBuilderString COUNT = new XContentBuilderString("count"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.COUNT, indexCount); + shards.toXContent(builder, params); + docs.toXContent(builder, params); + store.toXContent(builder, params); + fieldData.toXContent(builder, params); + filterCache.toXContent(builder, params); + idCache.toXContent(builder, params); + completion.toXContent(builder, params); + segments.toXContent(builder, params); + peroclate.toXContent(builder, params); + return builder; + } + + public static class ShardStats implements ToXContent, Streamable { + + int indices; + int total; + int primaries; + + // min/max + int minIndexShards = -1; + int maxIndexShards = -1; + int minIndexPrimaryShards = -1; + int maxIndexPrimaryShards = -1; + double minIndexReplication = -1; + double totalIndexReplication = 0; + double maxIndexReplication = -1; + + public ShardStats() { + } + + /** + * number of indices in the cluster + */ + public int indices() { + return this.indices; + } + + /** + * total number of shards in the cluster + */ + public int total() { + return this.total; + } + + /** + * total number of primary shards in the cluster + */ + public int primaries() { + return this.primaries; + } + + /** + * returns how many *redundant* copies of the data the cluster holds - running with no replicas will return 0 + */ + public double replication() { + if (primaries == 0) { + return 0; + } + return (((double) (total - primaries)) / primaries); + } + + /** the maximum number of shards (primary+replicas) an index has */ + public int maxIndexShards() { + return this.maxIndexShards; + } + + /** the minimum number of shards (primary+replicas) an index has */ + public int minIndexShards() { + return this.minIndexShards; + } + + /** average number of shards (primary+replicas) across the indices */ + public double avgIndexShards() { + if (this.indices == 0) { + return -1; + } + return ((double) this.total) / this.indices; + } + + /** + * the maximum number of primary shards an index has + */ + public int maxIndexPrimaryShards() { + return this.maxIndexPrimaryShards; + } + + /** the minimum number of primary shards an index has */ + public int minIndexPrimaryShards() { + return this.minIndexPrimaryShards; + } + + /** the average number primary shards across the indices */ + public double avgIndexPrimaryShards() { + if (this.indices == 0) { + return -1; + } + return ((double) this.primaries) / this.indices; + } + + /** + * minimum replication factor across the indices. See {@link #replication} + */ + public double minIndexReplication() { + return this.minIndexReplication; + } + + /** + * average replication factor across the indices. See {@link #replication} + */ + public double avgIndexReplication() { + if (indices == 0) { + return -1; + } + return this.totalIndexReplication / this.indices; + } + + /** + * maximum replication factor across the indices. See {@link #replication + */ + public double maxIndexReplication() { + return this.maxIndexReplication; + } + + public void addIndexShardCount(ShardStats indexShardCount) { + this.indices++; + this.primaries += indexShardCount.primaries; + this.total += indexShardCount.total; + this.totalIndexReplication += indexShardCount.replication(); + if (this.indices == 1) { + // first index, uninitialized. + minIndexPrimaryShards = indexShardCount.primaries; + maxIndexPrimaryShards = indexShardCount.primaries; + minIndexShards = indexShardCount.total; + maxIndexShards = indexShardCount.total; + minIndexReplication = indexShardCount.replication(); + maxIndexReplication = minIndexReplication; + } else { + minIndexShards = Math.min(minIndexShards, indexShardCount.total); + minIndexPrimaryShards = Math.min(minIndexPrimaryShards, indexShardCount.primaries); + minIndexReplication = Math.min(minIndexReplication, indexShardCount.replication()); + + maxIndexShards = Math.max(maxIndexShards, indexShardCount.total); + maxIndexPrimaryShards = Math.max(maxIndexPrimaryShards, indexShardCount.primaries); + maxIndexReplication = Math.max(maxIndexReplication, indexShardCount.replication()); + } + } + + public static ShardStats readShardStats(StreamInput in) throws IOException { + ShardStats c = new ShardStats(); + c.readFrom(in); + return c; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + indices = in.readVInt(); + total = in.readVInt(); + primaries = in.readVInt(); + minIndexShards = in.readVInt(); + maxIndexShards = in.readVInt(); + minIndexPrimaryShards = in.readVInt(); + maxIndexPrimaryShards = in.readVInt(); + minIndexReplication = in.readDouble(); + totalIndexReplication = in.readDouble(); + maxIndexReplication = in.readDouble(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(indices); + out.writeVInt(total); + out.writeVInt(primaries); + out.writeVInt(minIndexShards); + out.writeVInt(maxIndexShards); + out.writeVInt(minIndexPrimaryShards); + out.writeVInt(maxIndexPrimaryShards); + out.writeDouble(minIndexReplication); + out.writeDouble(totalIndexReplication); + out.writeDouble(maxIndexReplication); + } + + static final class Fields { + static final XContentBuilderString SHARDS = new XContentBuilderString("shards"); + static final XContentBuilderString TOTAL = new XContentBuilderString("total"); + static final XContentBuilderString PRIMARIES = new XContentBuilderString("primaries"); + static final XContentBuilderString REPLICATION = new XContentBuilderString("replication"); + static final XContentBuilderString MIN = new XContentBuilderString("min"); + static final XContentBuilderString MAX = new XContentBuilderString("max"); + static final XContentBuilderString AVG = new XContentBuilderString("avg"); + static final XContentBuilderString INDEX = new XContentBuilderString("index"); + } + + private void addIntMinMax(XContentBuilderString field, int min, int max, double avg, XContentBuilder builder) throws IOException { + builder.startObject(field); + builder.field(Fields.MIN, min); + builder.field(Fields.MAX, max); + builder.field(Fields.AVG, avg); + builder.endObject(); + } + + private void addDoubleMinMax(XContentBuilderString field, double min, double max, double avg, XContentBuilder builder) throws IOException { + builder.startObject(field); + builder.field(Fields.MIN, min); + builder.field(Fields.MAX, max); + builder.field(Fields.AVG, avg); + builder.endObject(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(Fields.SHARDS); + if (indices > 0) { + + builder.field(Fields.TOTAL, total); + builder.field(Fields.PRIMARIES, primaries); + builder.field(Fields.REPLICATION, replication()); + + builder.startObject(Fields.INDEX); + addIntMinMax(Fields.SHARDS, minIndexShards, maxIndexShards, avgIndexShards(), builder); + addIntMinMax(Fields.PRIMARIES, minIndexPrimaryShards, maxIndexPrimaryShards, avgIndexPrimaryShards(), builder); + addDoubleMinMax(Fields.REPLICATION, minIndexReplication, maxIndexReplication, avgIndexReplication(), builder); + builder.endObject(); + } + builder.endObject(); + return builder; + } + + @Override + public String toString() { + return "total [" + total + "] primaries [" + primaries + "]"; + } + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java new file mode 100644 index 0000000000000..597edbe986ecb --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodeResponse.java @@ -0,0 +1,88 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; +import org.elasticsearch.action.admin.indices.stats.ShardStats; +import org.elasticsearch.action.support.nodes.NodeOperationResponse; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +public class ClusterStatsNodeResponse extends NodeOperationResponse { + + private NodeInfo nodeInfo; + private NodeStats nodeStats; + private ShardStats[] shardsStats; + + ClusterStatsNodeResponse() { + } + + public ClusterStatsNodeResponse(DiscoveryNode node, NodeInfo nodeInfo, NodeStats nodeStats, ShardStats[] shardsStats) { + super(node); + this.nodeInfo = nodeInfo; + this.nodeStats = nodeStats; + this.shardsStats = shardsStats; + } + + public NodeInfo nodeInfo() { + return this.nodeInfo; + } + + public NodeStats nodeStats() { + return this.nodeStats; + } + + public ShardStats[] shardsStats() { + return this.shardsStats; + } + + public static ClusterStatsNodeResponse readNodeResponse(StreamInput in) throws IOException { + ClusterStatsNodeResponse nodeResponse = new ClusterStatsNodeResponse(); + nodeResponse.readFrom(in); + return nodeResponse; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + this.nodeInfo = NodeInfo.readNodeInfo(in); + this.nodeStats = NodeStats.readNodeStats(in); + int size = in.readVInt(); + shardsStats = new ShardStats[size]; + for (size--; size >= 0; size--) { + shardsStats[size] = ShardStats.readShardStats(in); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + nodeInfo.writeTo(out); + nodeStats.writeTo(out); + out.writeVInt(shardsStats.length); + for (ShardStats ss : shardsStats) { + ss.writeTo(out); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodes.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodes.java new file mode 100644 index 0000000000000..386b74a05a224 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsNodes.java @@ -0,0 +1,650 @@ +package org.elasticsearch.action.admin.cluster.stats; +/* + * 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. + */ + + +import com.carrotsearch.hppc.ObjectIntOpenHashMap; +import com.carrotsearch.hppc.cursors.ObjectIntCursor; +import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.PluginInfo; +import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; +import org.elasticsearch.monitor.fs.FsStats; +import org.elasticsearch.monitor.jvm.JvmInfo; +import org.elasticsearch.monitor.os.OsInfo; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.HashSet; +import java.util.Set; + +public class ClusterStatsNodes implements ToXContent, Streamable { + + private Counts counts; + private Set versions; + private OsStats os; + private ProcessStats process; + private JvmStats jvm; + private FsStats.Info fs; + private Set plugins; + + private ClusterStatsNodes() { + } + + public ClusterStatsNodes(ClusterStatsNodeResponse[] nodeResponses) { + this.counts = new Counts(); + this.versions = new HashSet(); + this.os = new OsStats(); + this.jvm = new JvmStats(); + this.fs = new FsStats.Info(); + this.plugins = new HashSet(); + this.process = new ProcessStats(); + + Set seenAddresses = new HashSet(nodeResponses.length); + + for (ClusterStatsNodeResponse nodeResponse : nodeResponses) { + + counts.addNodeInfo(nodeResponse.nodeInfo()); + versions.add(nodeResponse.nodeInfo().getVersion()); + process.addNodeStats(nodeResponse.nodeStats()); + jvm.addNodeInfoStats(nodeResponse.nodeInfo(), nodeResponse.nodeStats()); + plugins.addAll(nodeResponse.nodeInfo().getPlugins().getInfos()); + + // now do the stats that should be deduped by hardware (implemented by ip deduping) + TransportAddress publishAddress = nodeResponse.nodeInfo().getTransport().address().publishAddress(); + InetAddress inetAddress = null; + if (publishAddress.uniqueAddressTypeId() == 1) { + inetAddress = ((InetSocketTransportAddress) publishAddress).address().getAddress(); + } + + if (!seenAddresses.add(inetAddress)) { + continue; + } + + os.addNodeInfo(nodeResponse.nodeInfo()); + if (nodeResponse.nodeStats().getFs() != null) { + fs.add(nodeResponse.nodeStats().getFs().total()); + } + } + } + + + public Counts counts() { + return this.counts; + } + + public Set versions() { + return versions; + } + + public OsStats os() { + return os; + } + + public ProcessStats process() { + return process; + } + + public JvmStats jvm() { + return jvm; + } + + public FsStats.Info fs() { + return fs; + } + + public Set plugins() { + return plugins; + } + + + @Override + public void readFrom(StreamInput in) throws IOException { + counts = Counts.readCounts(in); + + int size = in.readVInt(); + versions = new HashSet(size); + for (; size > 0; size--) { + versions.add(Version.readVersion(in)); + } + + os = OsStats.readOsStats(in); + + jvm = JvmStats.readJvmStats(in); + + fs = FsStats.Info.readInfoFrom(in); + + size = in.readVInt(); + plugins = new HashSet(size); + for (; size > 0; size--) { + plugins.add(PluginInfo.readPluginInfo(in)); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + counts.writeTo(out); + out.writeVInt(versions.size()); + for (Version v : versions) Version.writeVersion(v, out); + os.writeTo(out); + jvm.writeTo(out); + fs.writeTo(out); + out.writeVInt(plugins.size()); + for (PluginInfo p : plugins) { + p.writeTo(out); + } + } + + public static ClusterStatsNodes readNodeStats(StreamInput in) throws IOException { + ClusterStatsNodes nodeStats = new ClusterStatsNodes(); + nodeStats.readFrom(in); + return nodeStats; + } + + static final class Fields { + static final XContentBuilderString COUNT = new XContentBuilderString("count"); + static final XContentBuilderString VERSIONS = new XContentBuilderString("versions"); + static final XContentBuilderString OS = new XContentBuilderString("os"); + static final XContentBuilderString PROCESS = new XContentBuilderString("process"); + static final XContentBuilderString JVM = new XContentBuilderString("jvm"); + static final XContentBuilderString FS = new XContentBuilderString("fs"); + static final XContentBuilderString PLUGINS = new XContentBuilderString("plugins"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(Fields.COUNT); + counts.toXContent(builder, params); + builder.endObject(); + + builder.startArray(Fields.VERSIONS); + for (Version v : versions) { + builder.value(v.toString()); + } + builder.endArray(); + + builder.startObject(Fields.OS); + os.toXContent(builder, params); + builder.endObject(); + + builder.startObject(Fields.PROCESS); + process.toXContent(builder, params); + builder.endObject(); + + builder.startObject(Fields.JVM); + jvm.toXContent(builder, params); + builder.endObject(); + + builder.field(Fields.FS); + fs.toXContent(builder, params); + + builder.startArray(Fields.PLUGINS); + for (PluginInfo pluginInfo : plugins) { + pluginInfo.toXContent(builder, params); + } + builder.endArray(); + return builder; + } + + public static class Counts implements Streamable, ToXContent { + int total; + int masterOnly; + int dataOnly; + int masterData; + int client; + + public void addNodeInfo(NodeInfo nodeInfo) { + total++; + DiscoveryNode node = nodeInfo.getNode(); + if (node.masterNode()) { + if (node.dataNode()) { + masterData++; + } else { + masterOnly++; + } + } else if (node.dataNode()) { + dataOnly++; + } else if (node.clientNode()) { + client++; + } + } + + public int total() { + return total; + } + + public int masterOnly() { + return masterOnly; + } + + public int dataOnly() { + return dataOnly; + } + + public int masterData() { + return masterData; + } + + public int client() { + return client; + } + + public static Counts readCounts(StreamInput in) throws IOException { + Counts c = new Counts(); + c.readFrom(in); + return c; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + total = in.readVInt(); + masterOnly = in.readVInt(); + dataOnly = in.readVInt(); + masterData = in.readVInt(); + client = in.readVInt(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(total); + out.writeVInt(masterOnly); + out.writeVInt(dataOnly); + out.writeVInt(masterData); + out.writeVInt(client); + } + + static final class Fields { + static final XContentBuilderString TOTAL = new XContentBuilderString("total"); + static final XContentBuilderString MASTER_ONLY = new XContentBuilderString("master_only"); + static final XContentBuilderString DATA_ONLY = new XContentBuilderString("data_only"); + static final XContentBuilderString MASTER_DATA = new XContentBuilderString("master_data"); + static final XContentBuilderString CLIENT = new XContentBuilderString("client"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.TOTAL, total); + builder.field(Fields.MASTER_ONLY, masterOnly); + builder.field(Fields.DATA_ONLY, dataOnly); + builder.field(Fields.MASTER_DATA, masterData); + builder.field(Fields.CLIENT, client); + return builder; + } + } + + public static class OsStats implements ToXContent, Streamable { + + int availableProcessors; + long availableMemory; + ObjectIntOpenHashMap cpus; + + public OsStats() { + cpus = new ObjectIntOpenHashMap(); + } + + public void addNodeInfo(NodeInfo nodeInfo) { + availableProcessors += nodeInfo.getOs().availableProcessors(); + cpus.addTo(nodeInfo.getOs().cpu(), 1); + if (nodeInfo.getOs().getMem().getTotal().bytes() != -1) { + availableMemory += nodeInfo.getOs().getMem().getTotal().bytes(); + } + } + + public int availableProcessors() { + return availableProcessors; + } + + public ByteSizeValue availableMemory() { + return new ByteSizeValue(availableMemory); + } + + public ObjectIntOpenHashMap cpus() { + return cpus; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + availableProcessors = in.readVInt(); + availableMemory = in.readLong(); + int size = in.readVInt(); + cpus = new ObjectIntOpenHashMap(size); + for (; size > 0; size--) { + cpus.addTo(OsInfo.Cpu.readCpu(in), in.readVInt()); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(availableProcessors); + out.writeLong(availableMemory); + out.writeVInt(cpus.size()); + for (ObjectIntCursor c : cpus) { + c.key.writeTo(out); + out.writeVInt(c.value); + } + + } + + public static OsStats readOsStats(StreamInput in) throws IOException { + OsStats os = new OsStats(); + os.readFrom(in); + return os; + } + + static final class Fields { + static final XContentBuilderString AVAILABLE_PROCESSORS = new XContentBuilderString("available_processors"); + static final XContentBuilderString MEM = new XContentBuilderString("mem"); + static final XContentBuilderString TOTAL = new XContentBuilderString("total"); + static final XContentBuilderString TOTAL_IN_BYTES = new XContentBuilderString("total_in_bytes"); + static final XContentBuilderString CPU = new XContentBuilderString("cpu"); + static final XContentBuilderString COUNT = new XContentBuilderString("count"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.AVAILABLE_PROCESSORS, availableProcessors); + builder.startObject(Fields.MEM); + builder.byteSizeField(Fields.TOTAL_IN_BYTES, Fields.TOTAL, availableMemory); + builder.endObject(); + + builder.startArray(Fields.CPU); + for (ObjectIntCursor cpu : cpus) { + builder.startObject(); + cpu.key.toXContent(builder, params); + builder.field(Fields.COUNT, cpu.value); + builder.endObject(); + } + builder.endArray(); + + return builder; + } + } + + public static class ProcessStats implements ToXContent, Streamable { + + int count; + int cpuPercent; + long totalOpenFileDescriptors; + + public void addNodeStats(NodeStats nodeStats) { + if (nodeStats.getProcess() == null) { + return; + } + count++; + cpuPercent += nodeStats.getProcess().cpu().getPercent(); + totalOpenFileDescriptors += nodeStats.getProcess().openFileDescriptors(); + } + + /** + * Cpu usage in percentages - 100 is 1 core. + */ + public int cpuPercent() { + return cpuPercent; + } + + public long avgOpenFileDescriptors() { + if (count == 0) { + return -1; + } + return totalOpenFileDescriptors / count; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + count = in.readVInt(); + cpuPercent = in.readVInt(); + totalOpenFileDescriptors = in.readLong(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(count); + out.writeVInt(cpuPercent); + out.writeVLong(totalOpenFileDescriptors); + } + + public static ProcessStats readStats(StreamInput in) throws IOException { + ProcessStats cpu = new ProcessStats(); + cpu.readFrom(in); + return cpu; + } + + static final class Fields { + static final XContentBuilderString CPU = new XContentBuilderString("cpu"); + static final XContentBuilderString PERCENT = new XContentBuilderString("percent"); + static final XContentBuilderString AVG_OPEN_FD = new XContentBuilderString("avg_open_file_descriptors"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(Fields.CPU).field(Fields.PERCENT, cpuPercent).endObject(); + builder.field(Fields.AVG_OPEN_FD, avgOpenFileDescriptors()); + return builder; + } + } + + public static class JvmStats implements Streamable, ToXContent { + + ObjectIntOpenHashMap versions; + long threads; + long maxUptime; + long heapUsed; + long heapMax; + + JvmStats() { + versions = new ObjectIntOpenHashMap(); + threads = 0; + maxUptime = 0; + heapMax = 0; + heapUsed = 0; + } + + public ObjectIntOpenHashMap versions() { + return versions; + } + + /** + * The total number of threads in the cluster + */ + public long threads() { + return threads; + } + + /** + * The maximum uptime of a node in the cluster + */ + public TimeValue maxUptime() { + return new TimeValue(maxUptime); + } + + /** + * Total heap used in the cluster + */ + public ByteSizeValue heapUsed() { + return new ByteSizeValue(heapUsed); + } + + /** + * Maximum total heap available to the cluster + */ + public ByteSizeValue heapMax() { + return new ByteSizeValue(heapMax); + } + + public void addNodeInfoStats(NodeInfo nodeInfo, NodeStats nodeStats) { + versions.addTo(new JvmVersion(nodeInfo.getJvm()), 1); + org.elasticsearch.monitor.jvm.JvmStats js = nodeStats.getJvm(); + if (js == null) { + return; + } + if (js.threads() != null) { + threads += js.threads().count(); + } + maxUptime = Math.max(maxUptime, js.uptime().millis()); + if (js.mem() != null) { + heapUsed += js.mem().getHeapUsed().bytes(); + heapMax += js.mem().getHeapMax().bytes(); + } + } + + @Override + public void readFrom(StreamInput in) throws IOException { + int size = in.readVInt(); + versions = new ObjectIntOpenHashMap(size); + for (; size > 0; size--) { + versions.addTo(JvmVersion.readJvmVersion(in), in.readVInt()); + } + threads = in.readVLong(); + maxUptime = in.readVLong(); + heapUsed = in.readVLong(); + heapMax = in.readVLong(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(versions.size()); + for (ObjectIntCursor v : versions) { + v.key.writeTo(out); + out.writeVInt(v.value); + } + + out.writeVLong(threads); + out.writeVLong(maxUptime); + out.writeVLong(heapUsed); + out.writeVLong(heapMax); + } + + public static JvmStats readJvmStats(StreamInput in) throws IOException { + JvmStats jvmStats = new JvmStats(); + jvmStats.readFrom(in); + return jvmStats; + } + + static final class Fields { + static final XContentBuilderString VERSIONS = new XContentBuilderString("versions"); + static final XContentBuilderString VERSION = new XContentBuilderString("version"); + static final XContentBuilderString VM_NAME = new XContentBuilderString("vm_name"); + static final XContentBuilderString VM_VERSION = new XContentBuilderString("vm_version"); + static final XContentBuilderString VM_VENDOR = new XContentBuilderString("vm_vendor"); + static final XContentBuilderString COUNT = new XContentBuilderString("count"); + static final XContentBuilderString THREADS = new XContentBuilderString("threads"); + static final XContentBuilderString MAX_UPTIME = new XContentBuilderString("max_uptime"); + static final XContentBuilderString MAX_UPTIME_IN_MILLIS = new XContentBuilderString("max_uptime_in_millis"); + static final XContentBuilderString MEM = new XContentBuilderString("mem"); + static final XContentBuilderString HEAP_USED = new XContentBuilderString("heap_used"); + static final XContentBuilderString HEAP_USED_IN_BYTES = new XContentBuilderString("heap_used_in_bytes"); + static final XContentBuilderString HEAP_MAX = new XContentBuilderString("heap_max"); + static final XContentBuilderString HEAP_MAX_IN_BYTES = new XContentBuilderString("heap_max_in_bytes"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.timeValueField(Fields.MAX_UPTIME_IN_MILLIS, Fields.MAX_UPTIME, maxUptime); + builder.startArray(Fields.VERSIONS); + for (ObjectIntCursor v : versions) { + builder.startObject(); + builder.field(Fields.VERSION, v.key.version); + builder.field(Fields.VM_NAME, v.key.vmName); + builder.field(Fields.VM_VERSION, v.key.vmVersion); + builder.field(Fields.VM_VENDOR, v.key.vmVendor); + builder.field(Fields.COUNT, v.value); + builder.endObject(); + } + builder.endArray(); + builder.startObject(Fields.MEM); + builder.byteSizeField(Fields.HEAP_USED_IN_BYTES, Fields.HEAP_USED, heapUsed); + builder.byteSizeField(Fields.HEAP_MAX_IN_BYTES, Fields.HEAP_MAX, heapMax); + builder.endObject(); + + builder.field(Fields.THREADS, threads); + return builder; + } + } + + public static class JvmVersion implements Streamable { + String version; + String vmName; + String vmVersion; + String vmVendor; + + JvmVersion(JvmInfo jvmInfo) { + version = jvmInfo.version(); + vmName = jvmInfo.vmName(); + vmVersion = jvmInfo.vmVersion(); + vmVendor = jvmInfo.vmVendor(); + } + + JvmVersion() { + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JvmVersion jvm = (JvmVersion) o; + + return vmVersion.equals(jvm.vmVersion) && vmVendor.equals(jvm.vmVendor); + } + + @Override + public int hashCode() { + return vmVersion.hashCode(); + } + + public static JvmVersion readJvmVersion(StreamInput in) throws IOException { + JvmVersion jvm = new JvmVersion(); + jvm.readFrom(in); + return jvm; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + version = in.readString(); + vmName = in.readString(); + vmVersion = in.readString(); + vmVendor = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(version); + out.writeString(vmName); + out.writeString(vmVersion); + out.writeString(vmVendor); + } + } + + +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequest.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequest.java new file mode 100644 index 0000000000000..1c0ed643c0a77 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequest.java @@ -0,0 +1,253 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; +import org.elasticsearch.action.support.nodes.NodesOperationRequest; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +/** + * A request to get node (cluster) level stats. + */ +public class ClusterStatsRequest extends NodesOperationRequest { + + private CommonStatsFlags indices = new CommonStatsFlags(); + private boolean os; + private boolean process; + private boolean jvm; + private boolean threadPool; + private boolean network; + private boolean fs; + private boolean transport; + private boolean http; + + /** + * Get stats from nodes based on the nodes ids specified. If none are passed, stats + * for all nodes will be returned. + */ + public ClusterStatsRequest(String... nodesIds) { + super(nodesIds); + } + + /** + * Sets all the request flags. + */ + public ClusterStatsRequest all() { + this.indices.all(); + this.os = true; + this.process = true; + this.jvm = true; + this.threadPool = true; + this.network = true; + this.fs = true; + this.transport = true; + this.http = true; + return this; + } + + /** + * Clears all the request flags. + */ + public ClusterStatsRequest clear() { + this.indices.clear(); + this.os = false; + this.process = false; + this.jvm = false; + this.threadPool = false; + this.network = false; + this.fs = false; + this.transport = false; + this.http = false; + return this; + } + + public CommonStatsFlags indices() { + return indices; + } + + public ClusterStatsRequest indices(CommonStatsFlags indices) { + this.indices = indices; + return this; + } + + /** + * Should indices stats be returned. + */ + public ClusterStatsRequest indices(boolean indices) { + if (indices) { + this.indices.all(); + } else { + this.indices.clear(); + } + return this; + } + + /** + * Should the node OS be returned. + */ + public boolean os() { + return this.os; + } + + /** + * Should the node OS be returned. + */ + public ClusterStatsRequest os(boolean os) { + this.os = os; + return this; + } + + /** + * Should the node Process be returned. + */ + public boolean process() { + return this.process; + } + + /** + * Should the node Process be returned. + */ + public ClusterStatsRequest process(boolean process) { + this.process = process; + return this; + } + + /** + * Should the node JVM be returned. + */ + public boolean jvm() { + return this.jvm; + } + + /** + * Should the node JVM be returned. + */ + public ClusterStatsRequest jvm(boolean jvm) { + this.jvm = jvm; + return this; + } + + /** + * Should the node Thread Pool be returned. + */ + public boolean threadPool() { + return this.threadPool; + } + + /** + * Should the node Thread Pool be returned. + */ + public ClusterStatsRequest threadPool(boolean threadPool) { + this.threadPool = threadPool; + return this; + } + + /** + * Should the node Network be returned. + */ + public boolean network() { + return this.network; + } + + /** + * Should the node Network be returned. + */ + public ClusterStatsRequest network(boolean network) { + this.network = network; + return this; + } + + /** + * Should the node file system stats be returned. + */ + public boolean fs() { + return this.fs; + } + + /** + * Should the node file system stats be returned. + */ + public ClusterStatsRequest fs(boolean fs) { + this.fs = fs; + return this; + } + + /** + * Should the node Transport be returned. + */ + public boolean transport() { + return this.transport; + } + + /** + * Should the node Transport be returned. + */ + public ClusterStatsRequest transport(boolean transport) { + this.transport = transport; + return this; + } + + /** + * Should the node HTTP be returned. + */ + public boolean http() { + return this.http; + } + + /** + * Should the node HTTP be returned. + */ + public ClusterStatsRequest http(boolean http) { + this.http = http; + return this; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + indices = CommonStatsFlags.readCommonStatsFlags(in); + os = in.readBoolean(); + process = in.readBoolean(); + jvm = in.readBoolean(); + threadPool = in.readBoolean(); + network = in.readBoolean(); + fs = in.readBoolean(); + transport = in.readBoolean(); + http = in.readBoolean(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + indices.writeTo(out); + out.writeBoolean(os); + out.writeBoolean(process); + out.writeBoolean(jvm); + out.writeBoolean(threadPool); + out.writeBoolean(network); + out.writeBoolean(fs); + out.writeBoolean(transport); + out.writeBoolean(http); + } + +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java new file mode 100644 index 0000000000000..124f02b7ae16b --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsRequestBuilder.java @@ -0,0 +1,137 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; +import org.elasticsearch.action.support.nodes.NodesOperationRequestBuilder; +import org.elasticsearch.client.ClusterAdminClient; +import org.elasticsearch.client.internal.InternalClusterAdminClient; + +/** + * + */ +public class ClusterStatsRequestBuilder extends NodesOperationRequestBuilder { + + public ClusterStatsRequestBuilder(ClusterAdminClient clusterClient) { + super((InternalClusterAdminClient) clusterClient, new ClusterStatsRequest()); + } + + /** + * Sets all the request flags. + */ + public ClusterStatsRequestBuilder all() { + request.all(); + return this; + } + + /** + * Clears all stats flags. + */ + public ClusterStatsRequestBuilder clear() { + request.clear(); + return this; + } + + /** + * Should the node indices stats be returned. + */ + public ClusterStatsRequestBuilder setIndices(boolean indices) { + request.indices(indices); + return this; + } + + /** + * Should the node indices stats be returned. + */ + public ClusterStatsRequestBuilder setIndices(CommonStatsFlags indices) { + request.indices(indices); + return this; + } + + /** + * Should the node OS stats be returned. + */ + public ClusterStatsRequestBuilder setOs(boolean os) { + request.os(os); + return this; + } + + /** + * Should the node OS stats be returned. + */ + public ClusterStatsRequestBuilder setProcess(boolean process) { + request.process(process); + return this; + } + + /** + * Should the node JVM stats be returned. + */ + public ClusterStatsRequestBuilder setJvm(boolean jvm) { + request.jvm(jvm); + return this; + } + + /** + * Should the node thread pool stats be returned. + */ + public ClusterStatsRequestBuilder setThreadPool(boolean threadPool) { + request.threadPool(threadPool); + return this; + } + + /** + * Should the node Network stats be returned. + */ + public ClusterStatsRequestBuilder setNetwork(boolean network) { + request.network(network); + return this; + } + + /** + * Should the node file system stats be returned. + */ + public ClusterStatsRequestBuilder setFs(boolean fs) { + request.fs(fs); + return this; + } + + /** + * Should the node Transport stats be returned. + */ + public ClusterStatsRequestBuilder setTransport(boolean transport) { + request.transport(transport); + return this; + } + + /** + * Should the node HTTP stats be returned. + */ + public ClusterStatsRequestBuilder setHttp(boolean http) { + request.http(http); + return this; + } + + @Override + protected void doExecute(ActionListener listener) { + ((ClusterAdminClient) client).clusterStats(request, listener); + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsResponse.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsResponse.java new file mode 100644 index 0000000000000..48c57dfb02a46 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsResponse.java @@ -0,0 +1,135 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import org.elasticsearch.action.support.nodes.NodesOperationResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; + +/** + * + */ +public class ClusterStatsResponse extends NodesOperationResponse implements ToXContent { + + + ClusterStatsNodes nodesStats; + ClusterStatsIndices indicesStats; + String clusterUUID; + + + ClusterStatsResponse() { + } + + public ClusterStatsResponse(ClusterName clusterName, String clusterUUID, ClusterStatsNodeResponse[] nodes) { + super(clusterName, null); + this.clusterUUID = clusterUUID; + nodesStats = new ClusterStatsNodes(nodes); + indicesStats = new ClusterStatsIndices(nodes); + } + + public ClusterStatsNodes nodesStats() { + return nodesStats; + } + + public ClusterStatsIndices indicesStats() { + return indicesStats; + } + + @Override + public ClusterStatsNodeResponse[] getNodes() { + throw new UnsupportedOperationException(); + } + + @Override + public Map getNodesMap() { + throw new UnsupportedOperationException(); + } + + @Override + public ClusterStatsNodeResponse getAt(int position) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + clusterUUID = in.readString(); + nodesStats = ClusterStatsNodes.readNodeStats(in); + indicesStats = ClusterStatsIndices.readIndicesStats(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(clusterUUID); + nodesStats.writeTo(out); + indicesStats.writeTo(out); + } + + static final class Fields { + static final XContentBuilderString NODES = new XContentBuilderString("nodes"); + static final XContentBuilderString INDICES = new XContentBuilderString("indices"); + static final XContentBuilderString UUID = new XContentBuilderString("uuid"); + static final XContentBuilderString CLUSTER_NAME = new XContentBuilderString("cluster_name"); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.CLUSTER_NAME, getClusterName().value()); + if (params.paramAsBoolean("output_uuid", false)) { + builder.field(Fields.UUID, clusterUUID); + } + + builder.startObject(Fields.INDICES); + indicesStats.toXContent(builder, params); + builder.endObject(); + builder.startObject(Fields.NODES); + nodesStats.toXContent(builder, params); + builder.endObject(); + return builder; + } + + @Override + public String toString() { + try { + XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); + builder.startObject(); + toXContent(builder, EMPTY_PARAMS); + builder.endObject(); + return builder.string(); + } catch (IOException e) { + return "{ \"error\" : \"" + e.getMessage() + "\"}"; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java b/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java new file mode 100644 index 0000000000000..df95023a6d1d2 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/stats/TransportClusterStatsAction.java @@ -0,0 +1,167 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.action.admin.cluster.stats; + +import com.google.common.collect.Lists; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; +import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags; +import org.elasticsearch.action.admin.indices.stats.ShardStats; +import org.elasticsearch.action.support.nodes.NodeOperationRequest; +import org.elasticsearch.action.support.nodes.TransportNodesOperationAction; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.service.IndexService; +import org.elasticsearch.index.shard.service.IndexShard; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.node.service.NodeService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReferenceArray; + +/** + * + */ +public class TransportClusterStatsAction extends TransportNodesOperationAction { + + private static final CommonStatsFlags SHARD_STATS_FLAGS = new CommonStatsFlags(CommonStatsFlags.Flag.Docs, CommonStatsFlags.Flag.Store, + CommonStatsFlags.Flag.FieldData, CommonStatsFlags.Flag.FilterCache, CommonStatsFlags.Flag.IdCache, + CommonStatsFlags.Flag.Completion, CommonStatsFlags.Flag.Segments, CommonStatsFlags.Flag.Percolate); + + private final NodeService nodeService; + private final IndicesService indicesService; + + + @Inject + public TransportClusterStatsAction(Settings settings, ClusterName clusterName, ThreadPool threadPool, + ClusterService clusterService, TransportService transportService, + NodeService nodeService, IndicesService indicesService) { + super(settings, clusterName, threadPool, clusterService, transportService); + this.nodeService = nodeService; + this.indicesService = indicesService; + } + + @Override + protected String executor() { + return ThreadPool.Names.MANAGEMENT; + } + + @Override + protected String transportAction() { + return ClusterStatsAction.NAME; + } + + @Override + protected ClusterStatsResponse newResponse(ClusterStatsRequest clusterStatsRequest, AtomicReferenceArray responses) { + final List nodeStats = Lists.newArrayList(); + for (int i = 0; i < responses.length(); i++) { + Object resp = responses.get(i); + if (resp instanceof ClusterStatsNodeResponse) { + nodeStats.add((ClusterStatsNodeResponse) resp); + } + } + return new ClusterStatsResponse(clusterName, clusterService.state().metaData().uuid(), nodeStats.toArray(new ClusterStatsNodeResponse[nodeStats.size()])); + } + + @Override + protected ClusterStatsRequest newRequest() { + return new ClusterStatsRequest(); + } + + @Override + protected ClusterStatsNodeRequest newNodeRequest() { + return new ClusterStatsNodeRequest(); + } + + @Override + protected ClusterStatsNodeRequest newNodeRequest(String nodeId, ClusterStatsRequest request) { + return new ClusterStatsNodeRequest(nodeId, request); + } + + @Override + protected ClusterStatsNodeResponse newNodeResponse() { + return new ClusterStatsNodeResponse(); + } + + @Override + protected ClusterStatsNodeResponse nodeOperation(ClusterStatsNodeRequest nodeRequest) throws ElasticSearchException { + NodeInfo nodeInfo = nodeService.info(false, true, false, true, false, false, true, false, true); + NodeStats nodeStats = nodeService.stats(CommonStatsFlags.NONE, false, true, true, false, false, true, false, false); + List shardsStats = new ArrayList(); + for (String index : indicesService.indices()) { + IndexService indexService = indicesService.indexService(index); + if (indexService == null) { + continue; + } + for (IndexShard indexShard : indexService) { + logger.warn("" + indexShard.shardId() + " " + indexShard.state()); + if (indexShard.routingEntry().active()) { + // only report on fully started shards + shardsStats.add(new ShardStats(indexShard, SHARD_STATS_FLAGS)); + } + } + + } + + return new ClusterStatsNodeResponse(nodeInfo.getNode(), nodeInfo, nodeStats, shardsStats.toArray(new ShardStats[shardsStats.size()])); + + } + + @Override + protected boolean accumulateExceptions() { + return false; + } + + static class ClusterStatsNodeRequest extends NodeOperationRequest { + + ClusterStatsRequest request; + + ClusterStatsNodeRequest() { + } + + ClusterStatsNodeRequest(String nodeId, ClusterStatsRequest request) { + super(request, nodeId); + this.request = request; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + request = new ClusterStatsRequest(); + request.readFrom(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + request.writeTo(out); + } + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java b/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java index 5ebd125eb49e1..b423a714c243e 100644 --- a/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java +++ b/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStatsFlags.java @@ -40,6 +40,20 @@ public class CommonStatsFlags implements Streamable, Cloneable { private String[] fieldDataFields = null; private String[] completionDataFields = null; + + /** + * @param flags flags to set. If no flags are supplied, default flags will be set. + */ + public CommonStatsFlags(Flag... flags) { + if (flags.length > 0) { + clear(); + for (Flag f : flags) { + this.flags.add(f); + } + } + } + + /** * Sets all flags to return all stats. */ @@ -136,6 +150,7 @@ void set(Flag flag) { flags.add(flag); } + public CommonStatsFlags set(Flag flag, boolean add) { if (add) { set(flag); diff --git a/src/main/java/org/elasticsearch/client/ClusterAdminClient.java b/src/main/java/org/elasticsearch/client/ClusterAdminClient.java index a5588222a66d6..6f3cd907b8e7e 100644 --- a/src/main/java/org/elasticsearch/client/ClusterAdminClient.java +++ b/src/main/java/org/elasticsearch/client/ClusterAdminClient.java @@ -72,6 +72,9 @@ import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequestBuilder; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequest; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequestBuilder; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksRequest; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksRequestBuilder; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksResponse; @@ -188,10 +191,30 @@ public interface ClusterAdminClient { */ NodesInfoRequestBuilder prepareNodesInfo(String... nodesIds); + /** + * Cluster wide aggregated stats. + * + * @param request The cluster stats request + * @return The result future + * @see org.elasticsearch.client.Requests#clusterStatsRequest + */ + ActionFuture clusterStats(ClusterStatsRequest request); + + /** + * Cluster wide aggregated stats + * + * @param request The cluster stats request + * @param listener A listener to be notified with a result + * @see org.elasticsearch.client.Requests#clusterStatsRequest() + */ + void clusterStats(ClusterStatsRequest request, ActionListener listener); + + ClusterStatsRequestBuilder prepareClusterStats(); + /** * Nodes stats of the cluster. * - * @param request The nodes info request + * @param request The nodes stats request * @return The result future * @see org.elasticsearch.client.Requests#nodesStatsRequest(String...) */ diff --git a/src/main/java/org/elasticsearch/client/Requests.java b/src/main/java/org/elasticsearch/client/Requests.java index e63c03e010b7c..91b1b128e3a79 100644 --- a/src/main/java/org/elasticsearch/client/Requests.java +++ b/src/main/java/org/elasticsearch/client/Requests.java @@ -35,6 +35,7 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest; import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexRequest; @@ -425,6 +426,16 @@ public static NodesStatsRequest nodesStatsRequest(String... nodesIds) { return new NodesStatsRequest(nodesIds); } + /** + * Creates a cluster stats request. + * + * @return The cluster stats request + * @see org.elasticsearch.client.ClusterAdminClient#clusterStats(org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequest) + */ + public static ClusterStatsRequest clusterStatsRequest() { + return new ClusterStatsRequest(); + } + /** * Shuts down all nodes in the cluster. */ diff --git a/src/main/java/org/elasticsearch/client/support/AbstractClusterAdminClient.java b/src/main/java/org/elasticsearch/client/support/AbstractClusterAdminClient.java index e4cbf9bfea880..177abf0e6b3f8 100644 --- a/src/main/java/org/elasticsearch/client/support/AbstractClusterAdminClient.java +++ b/src/main/java/org/elasticsearch/client/support/AbstractClusterAdminClient.java @@ -89,6 +89,10 @@ import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequestBuilder; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequest; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequestBuilder; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksRequest; import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksRequestBuilder; @@ -195,6 +199,21 @@ public NodesStatsRequestBuilder prepareNodesStats(String... nodesIds) { return new NodesStatsRequestBuilder(this).setNodesIds(nodesIds); } + @Override + public ActionFuture clusterStats(ClusterStatsRequest request) { + return execute(ClusterStatsAction.INSTANCE, request); + } + + @Override + public void clusterStats(ClusterStatsRequest request, ActionListener listener) { + execute(ClusterStatsAction.INSTANCE, request, listener); + } + + @Override + public ClusterStatsRequestBuilder prepareClusterStats() { + return new ClusterStatsRequestBuilder(this); + } + @Override public ActionFuture nodesHotThreads(NodesHotThreadsRequest request) { return execute(NodesHotThreadsAction.INSTANCE, request); diff --git a/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java b/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java index b14dd716828c4..26cb27c1d27f2 100644 --- a/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java +++ b/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java @@ -89,4 +89,5 @@ public void doWithNode(DiscoveryNode node, ActionListener listener) th } }, listener); } + } diff --git a/src/main/java/org/elasticsearch/monitor/fs/FsStats.java b/src/main/java/org/elasticsearch/monitor/fs/FsStats.java index 44c2c223acc30..bb2c227e4db4e 100644 --- a/src/main/java/org/elasticsearch/monitor/fs/FsStats.java +++ b/src/main/java/org/elasticsearch/monitor/fs/FsStats.java @@ -20,6 +20,7 @@ package org.elasticsearch.monitor.fs; import com.google.common.collect.Iterators; +import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; @@ -56,9 +57,19 @@ public static class Info implements Streamable, ToXContent { double diskQueue = -1; double diskServiceTime = -1; + static public Info readInfoFrom(StreamInput in) throws IOException { + Info i = new Info(); + i.readFrom(in); + return i; + } + @Override public void readFrom(StreamInput in) throws IOException { - path = in.readString(); + if (in.getVersion().after(Version.V_0_90_7)) { + path = in.readOptionalString(); + } else { + path = in.readString(); + } mount = in.readOptionalString(); dev = in.readOptionalString(); total = in.readLong(); @@ -74,7 +85,11 @@ public void readFrom(StreamInput in) throws IOException { @Override public void writeTo(StreamOutput out) throws IOException { - out.writeString(path); + if (out.getVersion().after(Version.V_0_90_7)) { + out.writeOptionalString(path); // total aggregates do not have a path + } else { + out.writeString(path); + } out.writeOptionalString(mount); out.writeOptionalString(dev); out.writeLong(total); @@ -324,8 +339,7 @@ public void readFrom(StreamInput in) throws IOException { timestamp = in.readVLong(); infos = new Info[in.readVInt()]; for (int i = 0; i < infos.length; i++) { - infos[i] = new Info(); - infos[i].readFrom(in); + infos[i] = Info.readInfoFrom(in); } } diff --git a/src/main/java/org/elasticsearch/monitor/os/OsInfo.java b/src/main/java/org/elasticsearch/monitor/os/OsInfo.java index a1a9a251ec7ad..89f62d28bf2a0 100644 --- a/src/main/java/org/elasticsearch/monitor/os/OsInfo.java +++ b/src/main/java/org/elasticsearch/monitor/os/OsInfo.java @@ -115,13 +115,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field(Fields.AVAILABLE_PROCESSORS, availableProcessors); if (cpu != null) { builder.startObject(Fields.CPU); - builder.field(Fields.VENDOR, cpu.vendor()); - builder.field(Fields.MODEL, cpu.model()); - builder.field(Fields.MHZ, cpu.mhz()); - builder.field(Fields.TOTAL_CORES, cpu.totalCores()); - builder.field(Fields.TOTAL_SOCKETS, cpu.totalSockets()); - builder.field(Fields.CORES_PER_SOCKET, cpu.coresPerSocket()); - builder.byteSizeField(Fields.CACHE_SIZE_IN_BYTES, Fields.CACHE_SIZE, cpu.cacheSize); + cpu.toXContent(builder, params); builder.endObject(); } if (mem != null) { @@ -251,7 +245,7 @@ public ByteSizeValue getTotal() { } - public static class Cpu implements Streamable, Serializable { + public static class Cpu implements Streamable, Serializable, ToXContent { String vendor = ""; String model = ""; @@ -348,5 +342,36 @@ public void writeTo(StreamOutput out) throws IOException { out.writeInt(coresPerSocket); out.writeLong(cacheSize); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Cpu cpu = (Cpu) o; + + return model.equals(cpu.model) && vendor.equals(cpu.vendor); + } + + @Override + public int hashCode() { + return model.hashCode(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.VENDOR, vendor); + builder.field(Fields.MODEL, model); + builder.field(Fields.MHZ, mhz); + builder.field(Fields.TOTAL_CORES, totalCores); + builder.field(Fields.TOTAL_SOCKETS, totalSockets); + builder.field(Fields.CORES_PER_SOCKET, coresPerSocket); + builder.byteSizeField(Fields.CACHE_SIZE_IN_BYTES, Fields.CACHE_SIZE, cacheSize); + return builder; + } } } diff --git a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index dc1fec94b0695..612628a911c06 100644 --- a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -41,6 +41,7 @@ import org.elasticsearch.rest.action.admin.cluster.snapshots.get.RestGetSnapshotsAction; import org.elasticsearch.rest.action.admin.cluster.snapshots.restore.RestRestoreSnapshotAction; import org.elasticsearch.rest.action.admin.cluster.state.RestClusterStateAction; +import org.elasticsearch.rest.action.admin.cluster.stats.RestClusterStatsAction; import org.elasticsearch.rest.action.admin.cluster.tasks.RestPendingClusterTasksAction; import org.elasticsearch.rest.action.admin.indices.alias.RestGetIndicesAliasesAction; import org.elasticsearch.rest.action.admin.indices.alias.RestIndicesAliasesAction; @@ -125,6 +126,7 @@ protected void configure() { bind(RestNodesHotThreadsAction.class).asEagerSingleton(); bind(RestNodesShutdownAction.class).asEagerSingleton(); bind(RestNodesRestartAction.class).asEagerSingleton(); + bind(RestClusterStatsAction.class).asEagerSingleton(); bind(RestClusterStateAction.class).asEagerSingleton(); bind(RestClusterHealthAction.class).asEagerSingleton(); bind(RestClusterUpdateSettingsAction.class).asEagerSingleton(); diff --git a/src/main/java/org/elasticsearch/rest/action/admin/cluster/stats/RestClusterStatsAction.java b/src/main/java/org/elasticsearch/rest/action/admin/cluster/stats/RestClusterStatsAction.java new file mode 100644 index 0000000000000..869fe531760f9 --- /dev/null +++ b/src/main/java/org/elasticsearch/rest/action/admin/cluster/stats/RestClusterStatsAction.java @@ -0,0 +1,75 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.rest.action.admin.cluster.stats; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsRequest; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.rest.*; +import org.elasticsearch.rest.action.support.RestXContentBuilder; + +import java.io.IOException; + + +/** + * + */ +public class RestClusterStatsAction extends BaseRestHandler { + + @Inject + public RestClusterStatsAction(Settings settings, Client client, RestController controller) { + super(settings, client); + controller.registerHandler(RestRequest.Method.GET, "/_cluster/stats", this); + controller.registerHandler(RestRequest.Method.GET, "/_cluster/stats/nodes/{nodeId}", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel) { + ClusterStatsRequest clusterStatsRequest = new ClusterStatsRequest().nodesIds(request.paramAsStringArray("nodeId", null)); + clusterStatsRequest.listenerThreaded(false); + client.admin().cluster().clusterStats(clusterStatsRequest, new ActionListener() { + @Override + public void onResponse(ClusterStatsResponse response) { + try { + XContentBuilder builder = RestXContentBuilder.restContentBuilder(request); + builder.startObject(); + response.toXContent(builder, request); + builder.endObject(); + channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder)); + } catch (Throwable e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + try { + channel.sendResponse(new XContentThrowableRestResponse(request, e)); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + } + }); + } +} diff --git a/src/test/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsTests.java b/src/test/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsTests.java new file mode 100644 index 0000000000000..0b46bda0b7cca --- /dev/null +++ b/src/test/java/org/elasticsearch/action/admin/cluster/stats/ClusterStatsTests.java @@ -0,0 +1,122 @@ +package org.elasticsearch.action.admin.cluster.stats; +/* + * 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. + */ + + +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import org.hamcrest.Matchers; +import org.junit.Test; + +@ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numNodes = 0) +public class ClusterStatsTests extends ElasticsearchIntegrationTest { + + private void assertCounts(ClusterStatsNodes.Counts counts, int total, int masterOnly, int dataOnly, int masterData, int client) { + assertThat(counts.total(), Matchers.equalTo(total)); + assertThat(counts.masterOnly(), Matchers.equalTo(masterOnly)); + assertThat(counts.dataOnly(), Matchers.equalTo(dataOnly)); + assertThat(counts.masterData(), Matchers.equalTo(masterData)); + assertThat(counts.client(), Matchers.equalTo(client)); + } + + @Test + public void testNodeCounts() { + cluster().startNode(); + ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + assertCounts(response.nodesStats().counts(), 1, 0, 0, 1, 0); + + cluster().startNode(ImmutableSettings.builder().put("node.data", false)); + response = client().admin().cluster().prepareClusterStats().get(); + assertCounts(response.nodesStats().counts(), 2, 1, 0, 1, 0); + + cluster().startNode(ImmutableSettings.builder().put("node.master", false)); + response = client().admin().cluster().prepareClusterStats().get(); + assertCounts(response.nodesStats().counts(), 3, 1, 1, 1, 0); + + cluster().startNode(ImmutableSettings.builder().put("node.client", true)); + response = client().admin().cluster().prepareClusterStats().get(); + assertCounts(response.nodesStats().counts(), 4, 1, 1, 1, 1); + } + + + private void assertShardStats(ClusterStatsIndices.ShardStats stats, int indices, int total, int primaries, double replicationFactor) { + assertThat(stats.indices(), Matchers.equalTo(indices)); + assertThat(stats.total(), Matchers.equalTo(total)); + assertThat(stats.primaries(), Matchers.equalTo(primaries)); + assertThat(stats.replication(), Matchers.equalTo(replicationFactor)); + } + + @Test + public void testIndicesShardStats() { + cluster().startNode(); + prepareCreate("test1").setSettings("number_of_shards", 2, "number_of_replicas", 1).get(); + ensureYellow(); + ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + assertThat(response.indicesStats.docs().getCount(), Matchers.equalTo(0l)); + assertThat(response.indicesStats.indexCount(), Matchers.equalTo(1)); + assertShardStats(response.indicesStats().shards(), 1, 2, 2, 0.0); + + // add another node, replicas should get assigned + cluster().startNode(); + ensureGreen(); + index("test1", "type", "1", "f", "f"); + refresh(); // make the doc visible + response = client().admin().cluster().prepareClusterStats().get(); + assertThat(response.indicesStats.docs().getCount(), Matchers.equalTo(1l)); + assertShardStats(response.indicesStats().shards(), 1, 4, 2, 1.0); + + prepareCreate("test2").setSettings("number_of_shards", 3, "number_of_replicas", 0).get(); + ensureGreen(); + response = client().admin().cluster().prepareClusterStats().get(); + assertThat(response.indicesStats.indexCount(), Matchers.equalTo(2)); + assertShardStats(response.indicesStats().shards(), 2, 7, 5, 2.0 / 5); + + assertThat(response.indicesStats().shards().avgIndexPrimaryShards(), Matchers.equalTo(2.5)); + assertThat(response.indicesStats().shards().minIndexPrimaryShards(), Matchers.equalTo(2)); + assertThat(response.indicesStats().shards().maxIndexPrimaryShards(), Matchers.equalTo(3)); + + assertThat(response.indicesStats().shards().avgIndexShards(), Matchers.equalTo(3.5)); + assertThat(response.indicesStats().shards().minIndexShards(), Matchers.equalTo(3)); + assertThat(response.indicesStats().shards().maxIndexShards(), Matchers.equalTo(4)); + + assertThat(response.indicesStats().shards().avgIndexReplication(), Matchers.equalTo(0.5)); + assertThat(response.indicesStats().shards().minIndexReplication(), Matchers.equalTo(0.0)); + assertThat(response.indicesStats().shards().maxIndexReplication(), Matchers.equalTo(1.0)); + + } + + @Test + public void testValuesSmokeScreen() { + cluster().ensureAtMostNumNodes(3); + index("test1", "type", "1", "f", "f"); + + ClusterStatsResponse response = client().admin().cluster().prepareClusterStats().get(); + assertThat(response.indicesStats.store().getSizeInBytes(), Matchers.greaterThan(0l)); + + assertThat(response.nodesStats.fs().getTotal().bytes(), Matchers.greaterThan(0l)); + assertThat(response.nodesStats.jvm().versions().size(), Matchers.greaterThan(0)); + assertThat(response.nodesStats.os().availableProcessors(), Matchers.greaterThan(0)); + assertThat(response.nodesStats.os().availableMemory().bytes(), Matchers.greaterThan(0l)); + assertThat(response.nodesStats.os().cpus().size(), Matchers.greaterThan(0)); + assertThat(response.nodesStats.versions().size(), Matchers.greaterThan(0)); + assertThat(response.nodesStats.plugins().size(), Matchers.greaterThanOrEqualTo(0)); + + } +}