Skip to content

Commit

Permalink
[7.11] Add index creation version stats to cluster stats (#68141) (#6…
Browse files Browse the repository at this point in the history
…8173)

This commit adds statistics about the index creation versions to the `/_cluster/stats` endpoint. The
stats look like:

```
{
  "_nodes" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "indices" : {
    "count" : 3,
    ...
    "versions" : [
      {
        "version" : "8.0.0",
        "index_count" : 1,
        "primary_shard_count" : 2,
        "total_primary_size" : "8.6kb",
        "total_primary_bytes" : 8831
      },
      {
        "version" : "7.11.0",
        "index_count" : 1,
        "primary_shard_count" : 1,
        "total_primary_size" : "4.6kb",
        "total_primary_bytes" : 4230
      }
    ]
  },
  ...
}
```

(`total_primary_size` is only shown with the `?human` flag)

This is useful for telemetry as it allows us to see if/when a cluster has indices created on a
previous version that would need to be either upgraded or supported during an upgrade.
  • Loading branch information
dakrone committed Jan 28, 2021
1 parent 1ffcec1 commit 72cc0d9
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 12 deletions.
11 changes: 10 additions & 1 deletion docs/reference/cluster/stats.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,16 @@ The API returns the following response:
"built_in_tokenizers": [],
"built_in_filters": [],
"built_in_analyzers": []
}
},
"versions": [
{
"version": "8.0.0",
"index_count": 1,
"primary_shard_count": 1,
"total_primary_size": "7.4kb",
"total_primary_bytes": 7632
}
]
},
"nodes": {
"count": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ public class ClusterStatsIndices implements ToXContentFragment {
private QueryCacheStats queryCache;
private CompletionStats completion;
private SegmentsStats segments;
private AnalysisStats analysis;
private MappingStats mappings;
private final AnalysisStats analysis;
private final MappingStats mappings;
private final VersionStats versions;

public ClusterStatsIndices(List<ClusterStatsNodeResponse> nodeResponses,
MappingStats mappingStats,
AnalysisStats analysisStats) {
public ClusterStatsIndices(List<ClusterStatsNodeResponse> nodeResponses, MappingStats mappingStats,
AnalysisStats analysisStats, VersionStats versionStats) {
ObjectObjectHashMap<String, ShardStats> countsPerIndex = new ObjectObjectHashMap<>();

this.docs = new DocsStats();
Expand Down Expand Up @@ -92,6 +92,7 @@ public ClusterStatsIndices(List<ClusterStatsNodeResponse> nodeResponses,

this.mappings = mappingStats;
this.analysis = analysisStats;
this.versions = versionStats;
}

public int getIndexCount() {
Expand Down Expand Up @@ -134,6 +135,10 @@ public AnalysisStats getAnalysis() {
return analysis;
}

public VersionStats getVersions() {
return versions;
}

static final class Fields {
static final String COUNT = "count";
}
Expand All @@ -154,6 +159,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
if (analysis != null) {
analysis.toXContent(builder, params);
}
if (versions != null) {
versions.toXContent(builder, params);
}
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ public ClusterStatsResponse(StreamInput in) throws IOException {
mappingStats = in.readOptionalWriteable(MappingStats::new);
analysisStats = in.readOptionalWriteable(AnalysisStats::new);
}
VersionStats versionStats = null;
if (in.getVersion().onOrAfter(Version.V_7_11_0)) {
versionStats = in.readOptionalWriteable(VersionStats::new);
}
this.clusterUUID = clusterUUID;

// built from nodes rather than from the stream directly
nodesStats = new ClusterStatsNodes(getNodes());
indicesStats = new ClusterStatsIndices(getNodes(), mappingStats, analysisStats);
indicesStats = new ClusterStatsIndices(getNodes(), mappingStats, analysisStats, versionStats);
}

public ClusterStatsResponse(long timestamp,
Expand All @@ -70,12 +74,13 @@ public ClusterStatsResponse(long timestamp,
List<ClusterStatsNodeResponse> nodes,
List<FailedNodeException> failures,
MappingStats mappingStats,
AnalysisStats analysisStats) {
AnalysisStats analysisStats,
VersionStats versionStats) {
super(clusterName, nodes, failures);
this.clusterUUID = clusterUUID;
this.timestamp = timestamp;
nodesStats = new ClusterStatsNodes(nodes);
indicesStats = new ClusterStatsIndices(nodes, mappingStats, analysisStats);
indicesStats = new ClusterStatsIndices(nodes, mappingStats, analysisStats, versionStats);
ClusterHealthStatus status = null;
for (ClusterStatsNodeResponse response : nodes) {
// only the master node populates the status
Expand Down Expand Up @@ -117,6 +122,9 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalWriteable(indicesStats.getMappings());
out.writeOptionalWriteable(indicesStats.getAnalysis());
}
if (out.getVersion().onOrAfter(Version.V_7_11_0)) {
out.writeOptionalWriteable(indicesStats.getVersions());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ protected ClusterStatsResponse newResponse(ClusterStatsRequest request,
}
}
}
VersionStats versionStats = VersionStats.of(metadata, responses);
return new ClusterStatsResponse(
System.currentTimeMillis(),
state.metadata().clusterUUID(),
clusterService.getClusterName(),
responses,
failures,
currentMappingStats,
currentAnalysisStats);
currentAnalysisStats,
versionStats);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.action.admin.cluster.stats;

import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.indices.stats.ShardStats;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/**
* {@link VersionStats} calculates statistics for index creation versions mapped to the number of
* indices, primary shards, and size of primary shards on disk. This is used from
* {@link ClusterStatsIndices} and exposed as part of the {@code "/_cluster/stats"} API.
*/
public final class VersionStats implements ToXContentFragment, Writeable {

private final Set<SingleVersionStats> versionStats;

public static VersionStats of(Metadata metadata, List<ClusterStatsNodeResponse> nodeResponses) {
final Map<Version, Integer> indexCounts = new HashMap<>();
final Map<Version, Integer> primaryShardCounts = new HashMap<>();
final Map<Version, Long> primaryByteCounts = new HashMap<>();
final Map<String, List<ShardStats>> indexPrimaryShardStats = new HashMap<>();

// Build a map from index name to primary shard stats
for (ClusterStatsNodeResponse r : nodeResponses) {
for (ShardStats shardStats : r.shardsStats()) {
if (shardStats.getShardRouting().primary()) {
indexPrimaryShardStats.compute(shardStats.getShardRouting().getIndexName(), (name, stats) -> {
if (stats == null) {
List<ShardStats> newStats = new ArrayList<>();
newStats.add(shardStats);
return newStats;
} else {
stats.add(shardStats);
return stats;
}
});
}
}
}

// Loop through all indices in the metadata, building the counts as needed
for (ObjectObjectCursor<String, IndexMetadata> cursor : metadata.indices()) {
IndexMetadata indexMetadata = cursor.value;
// Increment version-specific index counts
indexCounts.compute(indexMetadata.getCreationVersion(), (v, i) -> {
if (i == null) {
return 1;
} else {
return i + 1;
}
});
// Increment version-specific primary shard counts
primaryShardCounts.compute(indexMetadata.getCreationVersion(), (v, i) -> {
if (i == null) {
return indexMetadata.getNumberOfShards();
} else {
return i + indexMetadata.getNumberOfShards();
}
});
// Increment version-specific primary shard sizes
primaryByteCounts.compute(indexMetadata.getCreationVersion(), (v, i) -> {
String indexName = indexMetadata.getIndex().getName();
long indexPrimarySize = indexPrimaryShardStats.getOrDefault(indexName, Collections.emptyList()).stream()
.mapToLong(stats -> stats.getStats().getStore().sizeInBytes())
.sum();
if (i == null) {
return indexPrimarySize;
} else {
return i + indexPrimarySize;
}
});
}
List<SingleVersionStats> calculatedStats = new ArrayList<>(indexCounts.size());
for (Map.Entry<Version, Integer> indexVersionCount : indexCounts.entrySet()) {
Version v = indexVersionCount.getKey();
SingleVersionStats singleStats = new SingleVersionStats(v, indexVersionCount.getValue(),
primaryShardCounts.getOrDefault(v, 0), primaryByteCounts.getOrDefault(v, 0L));
calculatedStats.add(singleStats);
}
return new VersionStats(calculatedStats);
}

VersionStats(Collection<SingleVersionStats> versionStats) {
this.versionStats = Collections.unmodifiableSet(new TreeSet<>(versionStats));
}

VersionStats(StreamInput in) throws IOException {
this.versionStats = Collections.unmodifiableSet(new TreeSet<>(in.readList(SingleVersionStats::new)));
}

public Set<SingleVersionStats> versionStats() {
return this.versionStats;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray("versions");
for (SingleVersionStats stat : versionStats) {
stat.toXContent(builder, params);
}
builder.endArray();
return builder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeCollection(versionStats);
}

@Override
public int hashCode() {
return versionStats.hashCode();
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}

VersionStats other = (VersionStats) obj;
return versionStats.equals(other.versionStats);
}

@Override
public String toString() {
return Strings.toString(this);
}

static class SingleVersionStats implements ToXContentObject, Writeable, Comparable<SingleVersionStats> {

public final Version version;
public final int indexCount;
public final int primaryShardCount;
public final long totalPrimaryByteCount;

SingleVersionStats(Version version, int indexCount, int primaryShardCount, long totalPrimaryByteCount) {
this.version = version;
this.indexCount = indexCount;
this.primaryShardCount = primaryShardCount;
this.totalPrimaryByteCount = totalPrimaryByteCount;
}

SingleVersionStats(StreamInput in) throws IOException {
this.version = Version.readVersion(in);
this.indexCount = in.readVInt();
this.primaryShardCount = in.readVInt();
this.totalPrimaryByteCount = in.readVLong();
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("version", version.toString());
builder.field("index_count", indexCount);
builder.field("primary_shard_count", primaryShardCount);
builder.humanReadableField("total_primary_bytes", "total_primary_size", new ByteSizeValue(totalPrimaryByteCount));
builder.endObject();
return builder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
Version.writeVersion(this.version, out);
out.writeVInt(this.indexCount);
out.writeVInt(this.primaryShardCount);
out.writeVLong(this.totalPrimaryByteCount);
}

@Override
public int compareTo(SingleVersionStats o) {
if (this.equals(o)) {
return 0;
}
if (this.version.equals(o.version)) {
// never 0, this is to make the comparator consistent with equals
return -1;
}
return this.version.compareTo(o.version);
}

@Override
public int hashCode() {
return Objects.hash(version, indexCount, primaryShardCount, totalPrimaryByteCount);
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}

SingleVersionStats other = (SingleVersionStats) obj;
return version.equals(other.version) &&
indexCount == other.indexCount &&
primaryShardCount == other.primaryShardCount &&
totalPrimaryByteCount == other.totalPrimaryByteCount;
}

@Override
public String toString() {
return Strings.toString(this);
}
}
}

0 comments on commit 72cc0d9

Please sign in to comment.