diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStats.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStats.java index 9086b440ac2..3c7abd733a4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStats.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/CommonStats.java @@ -30,6 +30,7 @@ import org.elasticsearch.index.get.GetStats; import org.elasticsearch.index.indexing.IndexingStats; import org.elasticsearch.index.merge.MergeStats; import org.elasticsearch.index.refresh.RefreshStats; +import org.elasticsearch.index.search.stats.SearchStats; import org.elasticsearch.index.shard.DocsStats; import org.elasticsearch.index.store.StoreStats; @@ -47,6 +48,8 @@ public class CommonStats implements Streamable, ToXContent { @Nullable GetStats get; + @Nullable SearchStats search; + @Nullable MergeStats merge; @Nullable RefreshStats refresh; @@ -86,6 +89,14 @@ public class CommonStats implements Streamable, ToXContent { } else { get.add(stats.get()); } + if (search == null) { + if (stats.search() != null) { + search = new SearchStats(); + search.add(stats.search()); + } + } else { + search.add(stats.search()); + } if (merge == null) { if (stats.merge() != null) { merge = new MergeStats(); @@ -144,6 +155,14 @@ public class CommonStats implements Streamable, ToXContent { return get; } + @Nullable public SearchStats search() { + return search; + } + + @Nullable public SearchStats getSearch() { + return search; + } + @Nullable public MergeStats merge() { return merge; } @@ -187,6 +206,9 @@ public class CommonStats implements Streamable, ToXContent { if (in.readBoolean()) { get = GetStats.readGetStats(in); } + if (in.readBoolean()) { + search = SearchStats.readSearchStats(in); + } if (in.readBoolean()) { merge = MergeStats.readMergeStats(in); } @@ -223,6 +245,12 @@ public class CommonStats implements Streamable, ToXContent { out.writeBoolean(true); get.writeTo(out); } + if (search == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + search.writeTo(out); + } if (merge == null) { out.writeBoolean(false); } else { @@ -257,6 +285,9 @@ public class CommonStats implements Streamable, ToXContent { if (get != null) { get.toXContent(builder, params); } + if (search != null) { + search.toXContent(builder, params); + } if (merge != null) { merge.toXContent(builder, params); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequest.java index 43e1b3b05c6..cc58ffbcd96 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequest.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/IndicesStatsRequest.java @@ -40,10 +40,12 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { private boolean store = true; private boolean indexing = true; private boolean get = true; + private boolean search = true; private boolean merge = false; private boolean refresh = false; private boolean flush = false; private String[] types = null; + private String[] groups = null; public IndicesStatsRequest indices(String... indices) { this.indices = indices; @@ -58,9 +60,12 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { store = false; get = false; indexing = false; + search = false; merge = false; refresh = false; flush = false; + types = null; + groups = null; return this; } @@ -81,6 +86,19 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { return this.types; } + /** + * Sets specific search group stats to retrieve the stats for. Mainly affects search + * when enabled. + */ + public IndicesStatsRequest groups(String... groups) { + this.groups = groups; + return this; + } + + public String[] groups() { + return this.groups; + } + public IndicesStatsRequest docs(boolean docs) { this.docs = docs; return this; @@ -117,6 +135,15 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { return this.get; } + public IndicesStatsRequest search(boolean search) { + this.search = search; + return this; + } + + public boolean search() { + return this.search; + } + public IndicesStatsRequest merge(boolean merge) { this.merge = merge; return this; @@ -150,6 +177,7 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { out.writeBoolean(store); out.writeBoolean(indexing); out.writeBoolean(get); + out.writeBoolean(search); out.writeBoolean(merge); out.writeBoolean(flush); out.writeBoolean(refresh); @@ -161,6 +189,14 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { out.writeUTF(type); } } + if (groups == null) { + out.writeVInt(0); + } else { + out.writeVInt(groups.length); + for (String group : groups) { + out.writeUTF(group); + } + } } @Override public void readFrom(StreamInput in) throws IOException { @@ -169,6 +205,7 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { store = in.readBoolean(); indexing = in.readBoolean(); get = in.readBoolean(); + search = in.readBoolean(); merge = in.readBoolean(); flush = in.readBoolean(); refresh = in.readBoolean(); @@ -179,5 +216,12 @@ public class IndicesStatsRequest extends BroadcastOperationRequest { types[i] = in.readUTF(); } } + size = in.readVInt(); + if (size > 0) { + groups = new String[size]; + for (int i = 0; i < size; i++) { + groups[i] = in.readUTF(); + } + } } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/TransportIndicesStatsAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/TransportIndicesStatsAction.java index efa31c8554f..b2841953f52 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/TransportIndicesStatsAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/stats/TransportIndicesStatsAction.java @@ -138,6 +138,9 @@ public class TransportIndicesStatsAction extends TransportBroadcastOperationActi if (request.request.get()) { stats.stats.get = indexShard.getStats(); } + if (request.request.search()) { + stats.stats().search = indexShard.searchStats(request.request.groups()); + } if (request.request.merge()) { stats.stats.merge = indexShard.mergeStats(); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/SearchRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/SearchRequest.java index 8de969600d0..ebb083e3e5f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/SearchRequest.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/SearchRequest.java @@ -347,6 +347,10 @@ public class SearchRequest implements ActionRequest { * Allows to provide additional source that will be used as well. */ public SearchRequest extraSource(SearchSourceBuilder sourceBuilder) { + if (sourceBuilder == null) { + extraSource = null; + return this; + } BytesStream bos = sourceBuilder.buildAsUnsafeBytes(Requests.CONTENT_TYPE); this.extraSource = bos.underlyingBytes(); this.extraSourceOffset = 0; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/admin/indices/stats/IndicesStatsRequestBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/admin/indices/stats/IndicesStatsRequestBuilder.java index 389f4d392c1..40316963085 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/admin/indices/stats/IndicesStatsRequestBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/action/admin/indices/stats/IndicesStatsRequestBuilder.java @@ -62,6 +62,11 @@ public class IndicesStatsRequestBuilder extends BaseIndicesRequestBuilder typeStats() { + @Nullable public Map typeStats() { return this.typeStats; } - public static IndexingStats readIndexingStats(StreamInput in) throws IOException { - IndexingStats indexingStats = new IndexingStats(); - indexingStats.readFrom(in); - return indexingStats; - } - @Override public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { builder.startObject(Fields.INDEXING); totalStats.toXContent(builder, params); @@ -216,6 +210,12 @@ public class IndexingStats implements Streamable, ToXContent { static final XContentBuilderString DELETE_TIME_IN_MILLIS = new XContentBuilderString("delete_time_in_millis"); } + public static IndexingStats readIndexingStats(StreamInput in) throws IOException { + IndexingStats indexingStats = new IndexingStats(); + indexingStats.readFrom(in); + return indexingStats; + } + @Override public void readFrom(StreamInput in) throws IOException { totalStats = Stats.readStats(in); if (in.readBoolean()) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java new file mode 100644 index 00000000000..5f961e9e1b8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -0,0 +1,243 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.index.search.stats; + +import org.elasticsearch.common.Nullable; +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.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + */ +public class SearchStats implements Streamable, ToXContent { + + public static class Stats implements Streamable, ToXContent { + + private long queryCount; + private long queryTimeInMillis; + + private long fetchCount; + private long fetchTimeInMillis; + + Stats() { + + } + + public Stats(long queryCount, long queryTimeInMillis, long fetchCount, long fetchTimeInMillis) { + this.queryCount = queryCount; + this.queryTimeInMillis = queryTimeInMillis; + this.fetchCount = fetchCount; + this.fetchTimeInMillis = fetchTimeInMillis; + } + + public void add(Stats stats) { + queryCount += stats.queryCount; + queryTimeInMillis += stats.queryTimeInMillis; + + fetchCount += stats.fetchCount; + fetchTimeInMillis += stats.fetchTimeInMillis; + } + + public long queryCount() { + return queryCount; + } + + public long getQueryCount() { + return queryCount; + } + + public TimeValue queryTime() { + return new TimeValue(queryTimeInMillis); + } + + public long queryTimeInMillis() { + return queryTimeInMillis; + } + + public long getQueryTimeInMillis() { + return queryTimeInMillis; + } + + public long fetchCount() { + return fetchCount; + } + + public long getFetchCount() { + return fetchCount; + } + + public TimeValue fetchTime() { + return new TimeValue(fetchTimeInMillis); + } + + public long fetchTimeInMillis() { + return fetchTimeInMillis; + } + + public long getFetchTimeInMillis() { + return fetchTimeInMillis; + } + + public static Stats readStats(StreamInput in) throws IOException { + Stats stats = new Stats(); + stats.readFrom(in); + return stats; + } + + @Override public void readFrom(StreamInput in) throws IOException { + queryCount = in.readVLong(); + queryTimeInMillis = in.readVLong(); + + fetchCount = in.readVLong(); + fetchTimeInMillis = in.readVLong(); + } + + @Override public void writeTo(StreamOutput out) throws IOException { + out.writeVLong(queryCount); + out.writeVLong(queryTimeInMillis); + + out.writeVLong(fetchCount); + out.writeVLong(fetchTimeInMillis); + } + + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(Fields.QUERY_TOTAL, queryCount); + builder.field(Fields.QUERY_TIME, queryTime().toString()); + builder.field(Fields.QUERY_TIME_IN_MILLIS, queryTimeInMillis); + + builder.field(Fields.FETCH_TOTAL, fetchCount); + builder.field(Fields.FETCH_TIME, fetchTime().toString()); + builder.field(Fields.FETCH_TIME_IN_MILLIS, fetchTimeInMillis); + + return builder; + } + } + + private Stats totalStats; + + @Nullable Map groupStats; + + public SearchStats() { + totalStats = new Stats(); + } + + public SearchStats(Stats totalStats, @Nullable Map groupStats) { + this.totalStats = totalStats; + this.groupStats = groupStats; + } + + public void add(SearchStats searchStats) { + add(searchStats, true); + } + + public void add(SearchStats searchStats, boolean includeTypes) { + if (searchStats == null) { + return; + } + totalStats.add(searchStats.totalStats); + if (includeTypes && searchStats.groupStats != null && !searchStats.groupStats.isEmpty()) { + if (groupStats == null) { + groupStats = new HashMap(searchStats.groupStats.size()); + } + for (Map.Entry entry : searchStats.groupStats.entrySet()) { + Stats stats = groupStats.get(entry.getKey()); + if (stats == null) { + groupStats.put(entry.getKey(), entry.getValue()); + } else { + stats.add(entry.getValue()); + } + } + } + } + + public Stats total() { + return this.totalStats; + } + + @Nullable public Map groupStats() { + return this.groupStats; + } + + @Override public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + builder.startObject(Fields.SEARCH); + totalStats.toXContent(builder, params); + if (groupStats != null && !groupStats.isEmpty()) { + builder.startObject(Fields.GROUPS); + for (Map.Entry entry : groupStats.entrySet()) { + builder.startObject(entry.getKey(), XContentBuilder.FieldCaseConversion.NONE); + entry.getValue().toXContent(builder, params); + builder.endObject(); + } + builder.endObject(); + } + builder.endObject(); + return builder; + } + + static final class Fields { + static final XContentBuilderString SEARCH = new XContentBuilderString("search"); + static final XContentBuilderString GROUPS = new XContentBuilderString("groups"); + static final XContentBuilderString QUERY_TOTAL = new XContentBuilderString("query_total"); + static final XContentBuilderString QUERY_TIME = new XContentBuilderString("query_time"); + static final XContentBuilderString QUERY_TIME_IN_MILLIS = new XContentBuilderString("query_time_in_millis"); + static final XContentBuilderString FETCH_TOTAL = new XContentBuilderString("fetch_total"); + static final XContentBuilderString FETCH_TIME = new XContentBuilderString("fetch_time"); + static final XContentBuilderString FETCH_TIME_IN_MILLIS = new XContentBuilderString("fetch_time_in_millis"); + } + + public static SearchStats readSearchStats(StreamInput in) throws IOException { + SearchStats searchStats = new SearchStats(); + searchStats.readFrom(in); + return searchStats; + } + + @Override public void readFrom(StreamInput in) throws IOException { + totalStats = Stats.readStats(in); + if (in.readBoolean()) { + int size = in.readVInt(); + groupStats = new HashMap(size); + for (int i = 0; i < size; i++) { + groupStats.put(in.readUTF(), Stats.readStats(in)); + } + } + } + + @Override public void writeTo(StreamOutput out) throws IOException { + totalStats.writeTo(out); + if (groupStats == null || groupStats.isEmpty()) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeVInt(groupStats.size()); + for (Map.Entry entry : groupStats.entrySet()) { + out.writeUTF(entry.getKey()); + entry.getValue().writeTo(out); + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchModule.java new file mode 100644 index 00000000000..7e6b3fc1c49 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchModule.java @@ -0,0 +1,31 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.index.search.stats; + +import org.elasticsearch.common.inject.AbstractModule; + +/** + */ +public class ShardSearchModule extends AbstractModule { + + @Override protected void configure() { + bind(ShardSearchService.class).asEagerSingleton(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchService.java new file mode 100644 index 00000000000..408aa2cb60a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/ShardSearchService.java @@ -0,0 +1,129 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.index.search.stats; + +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.metrics.MeanMetric; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.settings.IndexSettings; +import org.elasticsearch.index.shard.AbstractIndexShardComponent; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.internal.SearchContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + */ +public class ShardSearchService extends AbstractIndexShardComponent { + + private final StatsHolder totalStats = new StatsHolder(); + + private volatile Map groupsStats = ImmutableMap.of(); + + @Inject public ShardSearchService(ShardId shardId, @IndexSettings Settings indexSettings) { + super(shardId, indexSettings); + } + + /** + * Returns the stats, including group specific stats. If the groups are null/0 length, then nothing + * is returned for them. If they are set, then only groups provided will be returned, or + * _all for all groups. + */ + public SearchStats stats(String... groups) { + SearchStats.Stats total = totalStats.stats(); + Map groupsSt = null; + if (groups != null && groups.length > 0) { + if (groups.length == 1 && groups[0].equals("_all")) { + groupsSt = new HashMap(groupsStats.size()); + for (Map.Entry entry : groupsStats.entrySet()) { + groupsSt.put(entry.getKey(), entry.getValue().stats()); + } + } else { + groupsSt = new HashMap(groups.length); + for (String group : groups) { + StatsHolder statsHolder = groupsStats.get(group); + if (statsHolder != null) { + groupsSt.put(group, statsHolder.stats()); + } + } + } + } + return new SearchStats(total, groupsSt); + } + + public void onQueryPhase(SearchContext searchContext, long tookInNanos) { + totalStats.queryMetric.inc(tookInNanos); + if (searchContext.groupStats() != null) { + for (int i = 0; i < searchContext.groupStats().size(); i++) { + groupStats(searchContext.groupStats().get(i)).queryMetric.inc(tookInNanos); + } + } + } + + public void onFetchPhase(SearchContext searchContext, long tookInNanos) { + totalStats.fetchMetric.inc(tookInNanos); + if (searchContext.groupStats() != null) { + for (int i = 0; i < searchContext.groupStats().size(); i++) { + groupStats(searchContext.groupStats().get(i)).fetchMetric.inc(tookInNanos); + } + } + } + + public void clear() { + totalStats.clear(); + synchronized (this) { + groupsStats = ImmutableMap.of(); + } + } + + private StatsHolder groupStats(String group) { + StatsHolder stats = groupsStats.get(group); + if (stats == null) { + synchronized (this) { + stats = groupsStats.get(group); + if (stats == null) { + stats = new StatsHolder(); + groupsStats = MapBuilder.newMapBuilder(groupsStats).put(group, stats).immutableMap(); + } + } + } + return stats; + } + + static class StatsHolder { + public final MeanMetric queryMetric = new MeanMetric(); + public final MeanMetric fetchMetric = new MeanMetric(); + + public SearchStats.Stats stats() { + return new SearchStats.Stats( + queryMetric.count(), TimeUnit.NANOSECONDS.toMillis(queryMetric.sum()), + fetchMetric.count(), TimeUnit.NANOSECONDS.toMillis(fetchMetric.sum())); + } + + public void clear() { + queryMetric.clear(); + fetchMetric.clear(); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/StatsGroupsParseElement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/StatsGroupsParseElement.java new file mode 100644 index 00000000000..ce69c71a4d6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/stats/StatsGroupsParseElement.java @@ -0,0 +1,46 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.index.search.stats; + +import org.elasticsearch.common.collect.ImmutableList; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchParseElement; +import org.elasticsearch.search.internal.SearchContext; + +import java.util.ArrayList; +import java.util.List; + +/** + */ +public class StatsGroupsParseElement implements SearchParseElement { + + @Override public void parse(XContentParser parser, SearchContext context) throws Exception { + XContentParser.Token token = parser.currentToken(); + if (token.isValue()) { + context.groupStats(ImmutableList.of(parser.text())); + } else if (token == XContentParser.Token.START_ARRAY) { + List groupStats = new ArrayList(4); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + groupStats.add(parser.text()); + } + context.groupStats(groupStats); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java index 35f351d8cb8..ce3882a5020 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java @@ -58,6 +58,7 @@ import org.elasticsearch.index.merge.policy.MergePolicyProvider; import org.elasticsearch.index.merge.scheduler.MergeSchedulerModule; import org.elasticsearch.index.percolator.PercolatorService; import org.elasticsearch.index.query.IndexQueryParserService; +import org.elasticsearch.index.search.stats.ShardSearchModule; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.IndexShardCreationException; import org.elasticsearch.index.shard.IndexShardManagement; @@ -282,6 +283,7 @@ public class InternalIndexService extends AbstractIndexComponent implements Inde modules.add(new ShardsPluginsModule(indexSettings, pluginsService)); modules.add(new IndexShardModule(shardId)); modules.add(new ShardIndexingModule()); + modules.add(new ShardSearchModule()); modules.add(new ShardGetModule()); modules.add(new StoreModule(indexSettings, injector.getInstance(IndexStore.class))); modules.add(new DeletionPolicyModule(indexSettings)); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java index 766e435f36d..5b1354a0319 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/IndexShard.java @@ -34,6 +34,8 @@ import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.merge.MergeStats; import org.elasticsearch.index.refresh.RefreshStats; +import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.ShardSearchService; import org.elasticsearch.index.shard.DocsStats; import org.elasticsearch.index.shard.IndexShardComponent; import org.elasticsearch.index.shard.IndexShardState; @@ -49,6 +51,8 @@ public interface IndexShard extends IndexShardComponent { ShardGetService getService(); + ShardSearchService searchService(); + ShardRouting routingEntry(); DocsStats docStats(); @@ -57,6 +61,8 @@ public interface IndexShard extends IndexShardComponent { IndexingStats indexingStats(String... types); + SearchStats searchStats(String... groups); + GetStats getStats(); MergeStats mergeStats(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java index 4978bd9aa95..d0e5de29537 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java @@ -60,6 +60,8 @@ import org.elasticsearch.index.merge.MergeStats; import org.elasticsearch.index.merge.scheduler.MergeSchedulerProvider; import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.refresh.RefreshStats; +import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.ShardSearchService; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.settings.IndexSettingsService; import org.elasticsearch.index.shard.*; @@ -109,6 +111,8 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I private final ShardIndexingService indexingService; + private final ShardSearchService searchService; + private final ShardGetService getService; private final Object mutex = new Object(); @@ -135,7 +139,7 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I private final MeanMetric flushMetric = new MeanMetric(); @Inject public InternalIndexShard(ShardId shardId, @IndexSettings Settings indexSettings, IndexSettingsService indexSettingsService, IndicesLifecycle indicesLifecycle, Store store, Engine engine, MergeSchedulerProvider mergeScheduler, Translog translog, - ThreadPool threadPool, MapperService mapperService, IndexQueryParserService queryParserService, IndexCache indexCache, IndexAliasesService indexAliasesService, ShardIndexingService indexingService, ShardGetService getService) { + ThreadPool threadPool, MapperService mapperService, IndexQueryParserService queryParserService, IndexCache indexCache, IndexAliasesService indexAliasesService, ShardIndexingService indexingService, ShardGetService getService, ShardSearchService searchService) { super(shardId, indexSettings); this.indicesLifecycle = (InternalIndicesLifecycle) indicesLifecycle; this.indexSettingsService = indexSettingsService; @@ -150,6 +154,7 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I this.indexAliasesService = indexAliasesService; this.indexingService = indexingService; this.getService = getService.setIndexShard(this); + this.searchService = searchService; state = IndexShardState.CREATED; this.refreshInterval = indexSettings.getAsTime("engine.robin.refresh_interval", indexSettings.getAsTime("index.refresh_interval", engine.defaultRefreshInterval())); @@ -186,6 +191,10 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I return this.getService; } + @Override public ShardSearchService searchService() { + return this.searchService; + } + @Override public ShardRouting routingEntry() { return this.shardRouting; } @@ -413,6 +422,10 @@ public class InternalIndexShard extends AbstractIndexShardComponent implements I return indexingService.stats(types); } + @Override public SearchStats searchStats(String... groups) { + return searchService.stats(groups); + } + @Override public GetStats getStats() { return getService.stats(); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java index 5662712041f..a98f130f0c3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java @@ -64,6 +64,7 @@ import org.elasticsearch.index.percolator.PercolatorService; import org.elasticsearch.index.query.IndexQueryParserModule; import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.refresh.RefreshStats; +import org.elasticsearch.index.search.stats.SearchStats; import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.service.InternalIndexService; import org.elasticsearch.index.settings.IndexSettingsModule; @@ -180,6 +181,7 @@ public class InternalIndicesService extends AbstractLifecycleComponent() { + @Override public void onResponse(IndicesStats response) { + try { + XContentBuilder builder = RestXContentBuilder.restContentBuilder(request); + builder.startObject(); + builder.field("ok", true); + buildBroadcastShardsHeader(builder, response); + response.toXContent(builder, request); + builder.endObject(); + channel.sendResponse(new XContentRestResponse(request, OK, builder)); + } catch (Exception 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); + } + } + }); + } + } + class RestGetStatsHandler implements RestHandler { @Override public void handleRequest(final RestRequest request, final RestChannel channel) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java index 4fc1091c955..8847ed9bf57 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java @@ -147,7 +147,7 @@ public class RestSearchAction extends BaseRestHandler { } private SearchSourceBuilder parseSearchSource(RestRequest request) { - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + SearchSourceBuilder searchSourceBuilder = null; String queryString = request.param("q"); if (queryString != null) { QueryStringQueryBuilder queryBuilder = QueryBuilders.queryString(queryString); @@ -165,24 +165,45 @@ public class RestSearchAction extends BaseRestHandler { throw new ElasticSearchIllegalArgumentException("Unsupported defaultOperator [" + defaultOperator + "], can either be [OR] or [AND]"); } } + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } searchSourceBuilder.query(queryBuilder); } int from = request.paramAsInt("from", -1); if (from != -1) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } searchSourceBuilder.from(from); } int size = request.paramAsInt("size", -1); if (size != -1) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } searchSourceBuilder.size(size); } - - searchSourceBuilder.explain(request.paramAsBooleanOptional("explain", null)); - searchSourceBuilder.version(request.paramAsBooleanOptional("version", null)); + if (request.hasParam("explain")) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } + searchSourceBuilder.explain(request.paramAsBooleanOptional("explain", null)); + } + if (request.hasParam("version")) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } + searchSourceBuilder.version(request.paramAsBooleanOptional("version", null)); + } String sField = request.param("fields"); if (sField != null) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } if (!Strings.hasText(sField)) { searchSourceBuilder.noFields(); } else { @@ -197,6 +218,9 @@ public class RestSearchAction extends BaseRestHandler { String sSorts = request.param("sort"); if (sSorts != null) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } String[] sorts = Strings.splitStringByCommaToArray(sSorts); for (String sort : sorts) { int delimiter = sort.lastIndexOf(":"); @@ -216,6 +240,9 @@ public class RestSearchAction extends BaseRestHandler { String sIndicesBoost = request.param("indices_boost"); if (sIndicesBoost != null) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } String[] indicesBoost = Strings.splitStringByCommaToArray(sIndicesBoost); for (String indexBoost : indicesBoost) { int divisor = indexBoost.indexOf(','); @@ -232,6 +259,14 @@ public class RestSearchAction extends BaseRestHandler { } } + String sStats = request.param("stats"); + if (sStats != null) { + if (searchSourceBuilder == null) { + searchSourceBuilder = new SearchSourceBuilder(); + } + searchSourceBuilder.stats(Strings.splitStringByCommaToArray(sStats)); + } + return searchSourceBuilder; } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java index 5ff635804b5..36e22eb315a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java @@ -37,6 +37,7 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.Index; import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.search.stats.StatsGroupsParseElement; import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.service.IndexShard; @@ -121,6 +122,7 @@ public class SearchService extends AbstractLifecycleComponent { elementParsers.putAll(dfsPhase.parseElements()); elementParsers.putAll(queryPhase.parseElements()); elementParsers.putAll(fetchPhase.parseElements()); + elementParsers.put("stats", new StatsGroupsParseElement()); this.elementParsers = ImmutableMap.copyOf(elementParsers); indicesLifecycle.addListener(indicesLifecycleListener); @@ -230,6 +232,7 @@ public class SearchService extends AbstractLifecycleComponent { SearchContext context = createContext(request); activeContexts.put(context.id(), context); try { + long time = System.nanoTime(); contextProcessing(context); queryPhase.execute(context); if (context.searchType() == SearchType.COUNT) { @@ -237,6 +240,7 @@ public class SearchService extends AbstractLifecycleComponent { } else { contextProcessedSuccessfully(context); } + context.indexShard().searchService().onQueryPhase(context, System.nanoTime() - time); return context.queryResult(); } catch (RuntimeException e) { logger.trace("Query phase failed", e); @@ -250,10 +254,12 @@ public class SearchService extends AbstractLifecycleComponent { public ScrollQuerySearchResult executeQueryPhase(InternalScrollSearchRequest request) throws ElasticSearchException { SearchContext context = findContext(request.id()); try { + long time = System.nanoTime(); contextProcessing(context); processScroll(request, context); queryPhase.execute(context); contextProcessedSuccessfully(context); + context.indexShard().searchService().onQueryPhase(context, System.nanoTime() - time); return new ScrollQuerySearchResult(context.queryResult(), context.shardTarget()); } catch (RuntimeException e) { logger.trace("Query phase failed", e); @@ -275,8 +281,10 @@ public class SearchService extends AbstractLifecycleComponent { throw new QueryPhaseExecutionException(context, "Failed to set aggregated df", e); } try { + long time = System.nanoTime(); queryPhase.execute(context); contextProcessedSuccessfully(context); + context.indexShard().searchService().onQueryPhase(context, System.nanoTime() - time); return context.queryResult(); } catch (RuntimeException e) { logger.trace("Query phase failed", e); @@ -292,7 +300,10 @@ public class SearchService extends AbstractLifecycleComponent { activeContexts.put(context.id(), context); contextProcessing(context); try { + long time = System.nanoTime(); queryPhase.execute(context); + long time2 = System.nanoTime(); + context.indexShard().searchService().onQueryPhase(context, time2 - time); shortcutDocIdsToLoad(context); fetchPhase.execute(context); if (context.scroll() == null) { @@ -300,6 +311,7 @@ public class SearchService extends AbstractLifecycleComponent { } else { contextProcessedSuccessfully(context); } + context.indexShard().searchService().onFetchPhase(context, System.nanoTime() - time2); return new QueryFetchSearchResult(context.queryResult(), context.fetchResult()); } catch (RuntimeException e) { logger.trace("Fetch phase failed", e); @@ -321,7 +333,10 @@ public class SearchService extends AbstractLifecycleComponent { throw new QueryPhaseExecutionException(context, "Failed to set aggregated df", e); } try { + long time = System.nanoTime(); queryPhase.execute(context); + long time2 = System.nanoTime(); + context.indexShard().searchService().onQueryPhase(context, time2 - time); shortcutDocIdsToLoad(context); fetchPhase.execute(context); if (context.scroll() == null) { @@ -329,6 +344,7 @@ public class SearchService extends AbstractLifecycleComponent { } else { contextProcessedSuccessfully(context); } + context.indexShard().searchService().onFetchPhase(context, System.nanoTime() - time2); return new QueryFetchSearchResult(context.queryResult(), context.fetchResult()); } catch (RuntimeException e) { logger.trace("Fetch phase failed", e); @@ -344,7 +360,10 @@ public class SearchService extends AbstractLifecycleComponent { contextProcessing(context); try { processScroll(request, context); + long time = System.nanoTime(); queryPhase.execute(context); + long time2 = System.nanoTime(); + context.indexShard().searchService().onQueryPhase(context, time2 - time); shortcutDocIdsToLoad(context); fetchPhase.execute(context); if (context.scroll() == null) { @@ -352,6 +371,7 @@ public class SearchService extends AbstractLifecycleComponent { } else { contextProcessedSuccessfully(context); } + context.indexShard().searchService().onFetchPhase(context, System.nanoTime() - time2); return new ScrollQueryFetchSearchResult(new QueryFetchSearchResult(context.queryResult(), context.fetchResult()), context.shardTarget()); } catch (RuntimeException e) { logger.trace("Fetch phase failed", e); @@ -367,12 +387,14 @@ public class SearchService extends AbstractLifecycleComponent { contextProcessing(context); try { context.docIdsToLoad(request.docIds(), 0, request.docIdsSize()); + long time = System.nanoTime(); fetchPhase.execute(context); if (context.scroll() == null) { freeContext(request.id()); } else { contextProcessedSuccessfully(context); } + context.indexShard().searchService().onFetchPhase(context, System.nanoTime() - time); return context.fetchResult(); } catch (RuntimeException e) { logger.trace("Fetch phase failed", e); @@ -399,7 +421,7 @@ public class SearchService extends AbstractLifecycleComponent { SearchShardTarget shardTarget = new SearchShardTarget(clusterService.localNode().id(), request.index(), request.shardId()); Engine.Searcher engineSearcher = indexShard.searcher(); - SearchContext context = new SearchContext(idGenerator.incrementAndGet(), shardTarget, request.searchType(), request.numberOfShards(), request.nowInMillis(), request.timeout(), request.types(), engineSearcher, indexService, scriptService); + SearchContext context = new SearchContext(idGenerator.incrementAndGet(), shardTarget, request.searchType(), request.numberOfShards(), request.nowInMillis(), request.timeout(), request.types(), engineSearcher, indexService, indexShard, scriptService); SearchContext.setCurrent(context); try { context.scroll(request.scroll()); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java index 43bbe132ca2..c94529a774f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java @@ -107,6 +107,8 @@ public class SearchSourceBuilder implements ToXContent { private TObjectFloatHashMap indexBoost = null; + private String[] stats; + /** * Constructs a new search source builder. @@ -475,6 +477,14 @@ public class SearchSourceBuilder implements ToXContent { return this; } + /** + * The stats groups this request will be aggregated under. + */ + public SearchSourceBuilder stats(String... statsGroups) { + this.stats = statsGroups; + return this; + } + @Override public String toString() { try { XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON).prettyPrint(); @@ -630,6 +640,14 @@ public class SearchSourceBuilder implements ToXContent { highlightBuilder.toXContent(builder, params); } + if (stats != null) { + builder.startArray("stats"); + for (String stat : stats) { + builder.value(stat); + } + builder.endArray(); + } + builder.endObject(); return builder; } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java index 59279fd892b..a0ef32800ac 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/internal/SearchContext.java @@ -24,6 +24,7 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.collect.Lists; import org.elasticsearch.common.lease.Releasable; @@ -38,6 +39,7 @@ import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.query.ParsedQuery; import org.elasticsearch.index.search.nested.BlockJoinQuery; import org.elasticsearch.index.service.IndexService; +import org.elasticsearch.index.shard.service.IndexShard; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.Scroll; @@ -86,6 +88,8 @@ public class SearchContext implements Releasable { private final ScriptService scriptService; + private final IndexShard indexShard; + private final IndexService indexService; private final ContextIndexSearcher searcher; @@ -103,6 +107,8 @@ public class SearchContext implements Releasable { private float queryBoost = 1.0f; + private List groupStats; + private Scroll scroll; private boolean explain; @@ -156,7 +162,7 @@ public class SearchContext implements Releasable { private Map nestedQueries; public SearchContext(long id, SearchShardTarget shardTarget, SearchType searchType, int numberOfShards, long nowInMillis, TimeValue timeout, - String[] types, Engine.Searcher engineSearcher, IndexService indexService, ScriptService scriptService) { + String[] types, Engine.Searcher engineSearcher, IndexService indexService, IndexShard indexShard, ScriptService scriptService) { this.id = id; this.nowInMillis = nowInMillis; this.searchType = searchType; @@ -169,6 +175,7 @@ public class SearchContext implements Releasable { this.dfsResult = new DfsSearchResult(id, shardTarget); this.queryResult = new QuerySearchResult(id, shardTarget); this.fetchResult = new FetchSearchResult(id, shardTarget); + this.indexShard = indexShard; this.indexService = indexService; this.searcher = new ContextIndexSearcher(this, engineSearcher); @@ -274,6 +281,10 @@ public class SearchContext implements Releasable { return this.searcher; } + public IndexShard indexShard() { + return this.indexShard; + } + public MapperService mapperService() { return indexService.mapperService(); } @@ -430,6 +441,14 @@ public class SearchContext implements Releasable { this.explain = explain; } + @Nullable public List groupStats() { + return this.groupStats; + } + + public void groupStats(List groupStats) { + this.groupStats = groupStats; + } + public boolean version() { return version; } diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/stats/SearchStatsTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/stats/SearchStatsTests.java new file mode 100644 index 00000000000..309c5c88001 --- /dev/null +++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/stats/SearchStatsTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search 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.test.integration.search.stats; + +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.elasticsearch.action.admin.indices.stats.IndicesStats; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.test.integration.AbstractNodesTests; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.elasticsearch.common.settings.ImmutableSettings.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +/** + */ +public class SearchStatsTests extends AbstractNodesTests { + + private Client client; + + @BeforeClass public void createNodes() throws Exception { + Settings settings = settingsBuilder().put("number_of_shards", 3).put("number_of_replicas", 0).build(); + startNode("server1", settings); + startNode("server2", settings); + client = getClient(); + } + + @AfterClass public void closeNodes() { + client.close(); + closeAllNodes(); + } + + protected Client getClient() { + return client("server1"); + } + + @Test public void testSimpleStats() throws Exception { + client.admin().indices().prepareDelete().execute().actionGet(); + + for (int i = 0; i < 100; i++) { + client.prepareIndex("test1", "type", Integer.toString(i)).setSource("field", "value").execute().actionGet(); + } + for (int i = 0; i < 100; i++) { + client.prepareIndex("test2", "type", Integer.toString(i)).setSource("field", "value").execute().actionGet(); + } + + for (int i = 0; i < 50; i++) { + client.prepareSearch().setQuery(QueryBuilders.termQuery("field", "value")).setStats("group1", "group2").execute().actionGet(); + } + + IndicesStats indicesStats = client.admin().indices().prepareStats().execute().actionGet(); + assertThat(indicesStats.total().search().total().queryCount(), greaterThan(0l)); + assertThat(indicesStats.total().search().total().queryTimeInMillis(), greaterThan(0l)); + assertThat(indicesStats.total().search().total().fetchCount(), greaterThan(0l)); + assertThat(indicesStats.total().search().total().fetchTimeInMillis(), greaterThan(0l)); + assertThat(indicesStats.total().search().groupStats(), nullValue()); + + indicesStats = client.admin().indices().prepareStats().setGroups("group1").execute().actionGet(); + assertThat(indicesStats.total().search().groupStats(), notNullValue()); + assertThat(indicesStats.total().search().groupStats().get("group1").queryCount(), greaterThan(0l)); + assertThat(indicesStats.total().search().groupStats().get("group1").queryTimeInMillis(), greaterThan(0l)); + assertThat(indicesStats.total().search().groupStats().get("group1").fetchCount(), greaterThan(0l)); + assertThat(indicesStats.total().search().groupStats().get("group1").fetchTimeInMillis(), greaterThan(0l)); + + NodesStatsResponse nodeStats = client.admin().cluster().prepareNodesStats().execute().actionGet(); + assertThat(nodeStats.nodes()[0].indices().search().total().queryCount(), greaterThan(0l)); + assertThat(nodeStats.nodes()[0].indices().search().total().queryTimeInMillis(), greaterThan(0l)); + } +}