/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.indexer.indices.stats;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.graylog2.rest.models.system.indexer.responses.IndexStats;
import org.graylog2.rest.models.system.indexer.responses.ShardRouting;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.MoreObjects.firstNonNull;
import static org.graylog2.indexer.gson.GsonUtils.asBoolean;
import static org.graylog2.indexer.gson.GsonUtils.asJsonArray;
import static org.graylog2.indexer.gson.GsonUtils.asJsonObject;
import static org.graylog2.indexer.gson.GsonUtils.asLong;
import static org.graylog2.indexer.gson.GsonUtils.asString;
@AutoValue
public abstract class IndexStatistics {
public abstract String index();
public abstract IndexStats primaryShards();
public abstract IndexStats allShards();
public abstract List<ShardRouting> routing();
public static IndexStatistics create(String index,
IndexStats primaryShards,
IndexStats allShards,
List<ShardRouting> routing) {
return new AutoValue_IndexStatistics(index, primaryShards, allShards, routing);
}
public static IndexStatistics create(String index, JsonObject indexStats) {
final JsonObject primaries = Optional.of(indexStats)
.map(json -> asJsonObject(json.get("primaries")))
.orElse(new JsonObject());
final JsonObject total = Optional.of(indexStats)
.map(json -> asJsonObject(json.get("total")))
.orElse(new JsonObject());
final JsonObject shards = Optional.of(indexStats)
.map(json -> asJsonObject(json.get("shards")))
.orElse(new JsonObject());
return create(index, buildIndexStats(primaries), buildIndexStats(total), buildShardRoutings(shards));
}
private static IndexStats buildIndexStats(final JsonObject stats) {
final Optional<JsonObject> flush = Optional.of(stats).map(json -> asJsonObject(json.get("flush")));
final long flushTotal = flush.map(json -> asLong(json.get("total"))).orElse(0L);
final long flushTotalTimeSeconds = flush.map(json -> asLong(json.get("total_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final Optional<JsonObject> get = Optional.of(stats).map(json -> asJsonObject(json.get("get")));
final long getTotal = get.map(json -> asLong(json.get("total"))).orElse(0L);
final long getTotalTimeSeconds = get.map(json -> asLong(json.get("total_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final Optional<JsonObject> indexing = Optional.of(stats).map(json -> asJsonObject(json.get("indexing")));
final long indexingTotal = indexing.map(json -> asLong(json.get("total"))).orElse(0L);
final long indexingTotalTimeSeconds = indexing.map(json -> asLong(json.get("total_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final Optional<JsonObject> merge = Optional.of(stats).map(json -> asJsonObject(json.get("merge")));
final long mergeTotal = merge.map(json -> asLong(json.get("total"))).orElse(0L);
final long mergeTotalTimeSeconds = merge.map(json -> asLong(json.get("total_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final Optional<JsonObject> refresh = Optional.of(stats).map(json -> asJsonObject(json.get("refresh")));
final long refreshTotal = refresh.map(json -> asLong(json.get("total"))).orElse(0L);
final long refreshTotalTimeSeconds = refresh.map(json -> asLong(json.get("total_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final Optional<JsonObject> search = Optional.of(stats).map(json -> asJsonObject(json.get("search")));
final long searchQueryTotal = search.map(json -> asLong(json.get("query_total"))).orElse(0L);
final long searchQueryTotalTimeSeconds = search.map(json -> asLong(json.get("query_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final long searchFetchTotal = search.map(json -> asLong(json.get("fetch_total"))).orElse(0L);
final long searchFetchTotalTimeSeconds = search.map(json -> asLong(json.get("fetch_time_in_millis"))).map(ms -> ms / 1000L).orElse(0L);
final long searchOpenContexts = search.map(json -> asLong(json.get("open_contexts"))).orElse(0L);
final long storeSizeInBytes = Optional.of(stats)
.map(json -> asJsonObject(json.get("store")))
.map(json -> asLong(json.get("size_in_bytes")))
.orElse(0L);
final long segmentsCount = Optional.of(stats)
.map(json -> asJsonObject(json.get("segments")))
.map(json -> asLong(json.get("count")))
.orElse(0L);
final Optional<JsonObject> docs = Optional.of(stats).map(json -> asJsonObject(json.get("docs")));
final long docsCount = docs.map(json -> asLong(json.get("count"))).orElse(0L);
final long docsDeleted = docs.map(json -> asLong(json.get("deleted"))).orElse(0L);
return IndexStats.create(
IndexStats.TimeAndTotalStats.create(flushTotal, flushTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(getTotal, getTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(indexingTotal, indexingTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(mergeTotal, mergeTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(refreshTotal, refreshTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(searchQueryTotal, searchQueryTotalTimeSeconds),
IndexStats.TimeAndTotalStats.create(searchFetchTotal, searchFetchTotalTimeSeconds),
searchOpenContexts,
storeSizeInBytes,
segmentsCount,
IndexStats.DocsStats.create(docsCount, docsDeleted)
);
}
private static List<ShardRouting> buildShardRoutings(JsonObject shardRoutings) {
final ImmutableList.Builder<ShardRouting> shardRoutingsBuilder = ImmutableList.builder();
for (Map.Entry<String, JsonElement> entry : shardRoutings.entrySet()) {
final int shardId = Integer.parseInt(entry.getKey());
final JsonArray shards = firstNonNull(asJsonArray(entry.getValue()), new JsonArray());
for (JsonElement jsonElement : shards) {
final Optional<JsonObject> routing = Optional.ofNullable(asJsonObject(jsonElement))
.map(json -> asJsonObject(json.get("routing")));
final String state = routing.map(json -> asString(json.get("state")))
.map(s -> s.toLowerCase(Locale.ENGLISH))
.orElse("unknown");
// Taken from org.elasticsearch.cluster.routing.ShardRouting
final boolean active = "started".equals(state) || "relocating".equals(state);
final boolean primary = routing.map(json -> asBoolean(json.get("primary"))).orElse(false);
final String nodeId = routing.map(json -> asString(json.get("node"))).orElse("Unknown");
// Node name and hostname should be filled when necessary (requiring an additional round trip to Elasticsearch)
final String nodeName = null;
final String nodeHostname = null;
final String relocatingNode = routing.map(json -> asString(json.get("relocating_node"))).orElse(null);
final ShardRouting shardRouting = ShardRouting.create(shardId, state, active, primary, nodeId, nodeName, nodeHostname, relocatingNode);
shardRoutingsBuilder.add(shardRouting);
}
}
return shardRoutingsBuilder.build();
}
}