/*
* 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.ObjectIntHashMap;
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.stats.NodeStats;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings;
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.monitor.fs.FsInfo;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.elasticsearch.plugins.PluginInfo;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class ClusterStatsNodes implements ToXContent {
private final Counts counts;
private final Set<Version> versions;
private final OsStats os;
private final ProcessStats process;
private final JvmStats jvm;
private final FsInfo.Path fs;
private final Set<PluginInfo> plugins;
private final NetworkTypes networkTypes;
ClusterStatsNodes(List<ClusterStatsNodeResponse> nodeResponses) {
this.versions = new HashSet<>();
this.fs = new FsInfo.Path();
this.plugins = new HashSet<>();
Set<InetAddress> seenAddresses = new HashSet<>(nodeResponses.size());
List<NodeInfo> nodeInfos = new ArrayList<>(nodeResponses.size());
List<NodeStats> nodeStats = new ArrayList<>(nodeResponses.size());
for (ClusterStatsNodeResponse nodeResponse : nodeResponses) {
nodeInfos.add(nodeResponse.nodeInfo());
nodeStats.add(nodeResponse.nodeStats());
this.versions.add(nodeResponse.nodeInfo().getVersion());
this.plugins.addAll(nodeResponse.nodeInfo().getPlugins().getPluginInfos());
// now do the stats that should be deduped by hardware (implemented by ip deduping)
TransportAddress publishAddress =
nodeResponse.nodeInfo().getTransport().address().publishAddress();
final InetAddress inetAddress = publishAddress.address().getAddress();
if (!seenAddresses.add(inetAddress)) {
continue;
}
if (nodeResponse.nodeStats().getFs() != null) {
this.fs.add(nodeResponse.nodeStats().getFs().getTotal());
}
}
this.counts = new Counts(nodeInfos);
this.os = new OsStats(nodeInfos, nodeStats);
this.process = new ProcessStats(nodeStats);
this.jvm = new JvmStats(nodeInfos, nodeStats);
this.networkTypes = new NetworkTypes(nodeInfos);
}
public Counts getCounts() {
return this.counts;
}
public Set<Version> getVersions() {
return versions;
}
public OsStats getOs() {
return os;
}
public ProcessStats getProcess() {
return process;
}
public JvmStats getJvm() {
return jvm;
}
public FsInfo.Path getFs() {
return fs;
}
public Set<PluginInfo> getPlugins() {
return plugins;
}
static final class Fields {
static final String COUNT = "count";
static final String VERSIONS = "versions";
static final String OS = "os";
static final String PROCESS = "process";
static final String JVM = "jvm";
static final String FS = "fs";
static final String PLUGINS = "plugins";
static final String NETWORK_TYPES = "network_types";
}
@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();
builder.startObject(Fields.NETWORK_TYPES);
networkTypes.toXContent(builder, params);
builder.endObject();
return builder;
}
public static class Counts implements ToXContent {
static final String COORDINATING_ONLY = "coordinating_only";
private final int total;
private final Map<String, Integer> roles;
private Counts(List<NodeInfo> nodeInfos) {
this.roles = new HashMap<>();
for (DiscoveryNode.Role role : DiscoveryNode.Role.values()) {
this.roles.put(role.getRoleName(), 0);
}
this.roles.put(COORDINATING_ONLY, 0);
int total = 0;
for (NodeInfo nodeInfo : nodeInfos) {
total++;
if (nodeInfo.getNode().getRoles().isEmpty()) {
Integer count = roles.get(COORDINATING_ONLY);
roles.put(COORDINATING_ONLY, ++count);
} else {
for (DiscoveryNode.Role role : nodeInfo.getNode().getRoles()) {
Integer count = roles.get(role.getRoleName());
roles.put(role.getRoleName(), ++count);
}
}
}
this.total = total;
}
public int getTotal() {
return total;
}
public Map<String, Integer> getRoles() {
return roles;
}
static final class Fields {
static final String TOTAL = "total";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params)
throws IOException {
builder.field(Fields.TOTAL, total);
for (Map.Entry<String, Integer> entry : roles.entrySet()) {
builder.field(entry.getKey(), entry.getValue());
}
return builder;
}
}
public static class OsStats implements ToXContent {
final int availableProcessors;
final int allocatedProcessors;
final ObjectIntHashMap<String> names;
final org.elasticsearch.monitor.os.OsStats.Mem mem;
/**
* Build the stats from information about each node.
*/
private OsStats(List<NodeInfo> nodeInfos, List<NodeStats> nodeStatsList) {
this.names = new ObjectIntHashMap<>();
int availableProcessors = 0;
int allocatedProcessors = 0;
for (NodeInfo nodeInfo : nodeInfos) {
availableProcessors += nodeInfo.getOs().getAvailableProcessors();
allocatedProcessors += nodeInfo.getOs().getAllocatedProcessors();
if (nodeInfo.getOs().getName() != null) {
names.addTo(nodeInfo.getOs().getName(), 1);
}
}
this.availableProcessors = availableProcessors;
this.allocatedProcessors = allocatedProcessors;
long totalMemory = 0;
long freeMemory = 0;
for (NodeStats nodeStats : nodeStatsList) {
if (nodeStats.getOs() != null) {
long total = nodeStats.getOs().getMem().getTotal().getBytes();
if (total > 0) {
totalMemory += total;
}
long free = nodeStats.getOs().getMem().getFree().getBytes();
if (free > 0) {
freeMemory += free;
}
}
}
this.mem = new org.elasticsearch.monitor.os.OsStats.Mem(totalMemory, freeMemory);
}
public int getAvailableProcessors() {
return availableProcessors;
}
public int getAllocatedProcessors() {
return allocatedProcessors;
}
public org.elasticsearch.monitor.os.OsStats.Mem getMem() {
return mem;
}
static final class Fields {
static final String AVAILABLE_PROCESSORS = "available_processors";
static final String ALLOCATED_PROCESSORS = "allocated_processors";
static final String NAME = "name";
static final String NAMES = "names";
static final String COUNT = "count";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params)
throws IOException {
builder.field(Fields.AVAILABLE_PROCESSORS, availableProcessors);
builder.field(Fields.ALLOCATED_PROCESSORS, allocatedProcessors);
builder.startArray(Fields.NAMES);
for (ObjectIntCursor<String> name : names) {
builder.startObject();
builder.field(Fields.NAME, name.key);
builder.field(Fields.COUNT, name.value);
builder.endObject();
}
builder.endArray();
mem.toXContent(builder, params);
return builder;
}
}
public static class ProcessStats implements ToXContent {
final int count;
final int cpuPercent;
final long totalOpenFileDescriptors;
final long minOpenFileDescriptors;
final long maxOpenFileDescriptors;
/**
* Build from looking at a list of node statistics.
*/
private ProcessStats(List<NodeStats> nodeStatsList) {
int count = 0;
int cpuPercent = 0;
long totalOpenFileDescriptors = 0;
long minOpenFileDescriptors = Long.MAX_VALUE;
long maxOpenFileDescriptors = Long.MIN_VALUE;
for (NodeStats nodeStats : nodeStatsList) {
if (nodeStats.getProcess() == null) {
continue;
}
count++;
if (nodeStats.getProcess().getCpu() != null) {
cpuPercent += nodeStats.getProcess().getCpu().getPercent();
}
long fd = nodeStats.getProcess().getOpenFileDescriptors();
if (fd > 0) {
// fd can be -1 if not supported on platform
totalOpenFileDescriptors += fd;
}
// we still do min max calc on -1, so we'll have an indication
// of it not being supported on one of the nodes.
minOpenFileDescriptors = Math.min(minOpenFileDescriptors, fd);
maxOpenFileDescriptors = Math.max(maxOpenFileDescriptors, fd);
}
this.count = count;
this.cpuPercent = cpuPercent;
this.totalOpenFileDescriptors = totalOpenFileDescriptors;
this.minOpenFileDescriptors = minOpenFileDescriptors;
this.maxOpenFileDescriptors = maxOpenFileDescriptors;
}
/**
* Cpu usage in percentages - 100 is 1 core.
*/
public int getCpuPercent() {
return cpuPercent;
}
public long getAvgOpenFileDescriptors() {
if (count == 0) {
return -1;
}
return totalOpenFileDescriptors / count;
}
public long getMaxOpenFileDescriptors() {
if (count == 0) {
return -1;
}
return maxOpenFileDescriptors;
}
public long getMinOpenFileDescriptors() {
if (count == 0) {
return -1;
}
return minOpenFileDescriptors;
}
static final class Fields {
static final String CPU = "cpu";
static final String PERCENT = "percent";
static final String OPEN_FILE_DESCRIPTORS = "open_file_descriptors";
static final String MIN = "min";
static final String MAX = "max";
static final String AVG = "avg";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params)
throws IOException {
builder.startObject(Fields.CPU).field(Fields.PERCENT, cpuPercent).endObject();
if (count > 0) {
builder.startObject(Fields.OPEN_FILE_DESCRIPTORS);
builder.field(Fields.MIN, getMinOpenFileDescriptors());
builder.field(Fields.MAX, getMaxOpenFileDescriptors());
builder.field(Fields.AVG, getAvgOpenFileDescriptors());
builder.endObject();
}
return builder;
}
}
public static class JvmStats implements ToXContent {
private final ObjectIntHashMap<JvmVersion> versions;
private final long threads;
private final long maxUptime;
private final long heapUsed;
private final long heapMax;
/**
* Build from lists of information about each node.
*/
private JvmStats(List<NodeInfo> nodeInfos, List<NodeStats> nodeStatsList) {
this.versions = new ObjectIntHashMap<>();
long threads = 0;
long maxUptime = 0;
long heapMax = 0;
long heapUsed = 0;
for (NodeInfo nodeInfo : nodeInfos) {
versions.addTo(new JvmVersion(nodeInfo.getJvm()), 1);
}
for (NodeStats nodeStats : nodeStatsList) {
org.elasticsearch.monitor.jvm.JvmStats js = nodeStats.getJvm();
if (js == null) {
continue;
}
if (js.getThreads() != null) {
threads += js.getThreads().getCount();
}
maxUptime = Math.max(maxUptime, js.getUptime().millis());
if (js.getMem() != null) {
heapUsed += js.getMem().getHeapUsed().getBytes();
heapMax += js.getMem().getHeapMax().getBytes();
}
}
this.threads = threads;
this.maxUptime = maxUptime;
this.heapUsed = heapUsed;
this.heapMax = heapMax;
}
public ObjectIntHashMap<JvmVersion> getVersions() {
return versions;
}
/**
* The total number of threads in the cluster
*/
public long getThreads() {
return threads;
}
/**
* The maximum uptime of a node in the cluster
*/
public TimeValue getMaxUpTime() {
return new TimeValue(maxUptime);
}
/**
* Total heap used in the cluster
*/
public ByteSizeValue getHeapUsed() {
return new ByteSizeValue(heapUsed);
}
/**
* Maximum total heap available to the cluster
*/
public ByteSizeValue getHeapMax() {
return new ByteSizeValue(heapMax);
}
static final class Fields {
static final String VERSIONS = "versions";
static final String VERSION = "version";
static final String VM_NAME = "vm_name";
static final String VM_VERSION = "vm_version";
static final String VM_VENDOR = "vm_vendor";
static final String COUNT = "count";
static final String THREADS = "threads";
static final String MAX_UPTIME = "max_uptime";
static final String MAX_UPTIME_IN_MILLIS = "max_uptime_in_millis";
static final String MEM = "mem";
static final String HEAP_USED = "heap_used";
static final String HEAP_USED_IN_BYTES = "heap_used_in_bytes";
static final String HEAP_MAX = "heap_max";
static final String HEAP_MAX_IN_BYTES = "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<JvmVersion> 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 {
String version;
String vmName;
String vmVersion;
String vmVendor;
JvmVersion(JvmInfo jvmInfo) {
version = jvmInfo.version();
vmName = jvmInfo.getVmName();
vmVersion = jvmInfo.getVmVersion();
vmVendor = jvmInfo.getVmVendor();
}
@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();
}
}
static class NetworkTypes implements ToXContent {
private final Map<String, AtomicInteger> transportTypes;
private final Map<String, AtomicInteger> httpTypes;
NetworkTypes(final List<NodeInfo> nodeInfos) {
final Map<String, AtomicInteger> transportTypes = new HashMap<>();
final Map<String, AtomicInteger> httpTypes = new HashMap<>();
for (final NodeInfo nodeInfo : nodeInfos) {
final Settings settings = nodeInfo.getSettings();
final String transportType =
settings.get(NetworkModule.TRANSPORT_TYPE_KEY,
NetworkModule.TRANSPORT_DEFAULT_TYPE_SETTING.get(settings));
final String httpType =
settings.get(NetworkModule.HTTP_TYPE_KEY,
NetworkModule.HTTP_DEFAULT_TYPE_SETTING.get(settings));
if (Strings.hasText(transportType)) {
transportTypes.computeIfAbsent(transportType,
k -> new AtomicInteger()).incrementAndGet();
}
if (Strings.hasText(httpType)) {
httpTypes.computeIfAbsent(httpType,
k -> new AtomicInteger()).incrementAndGet();
}
}
this.transportTypes = Collections.unmodifiableMap(transportTypes);
this.httpTypes = Collections.unmodifiableMap(httpTypes);
}
@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject("transport_types");
for (final Map.Entry<String, AtomicInteger> entry : transportTypes.entrySet()) {
builder.field(entry.getKey(), entry.getValue().get());
}
builder.endObject();
builder.startObject("http_types");
for (final Map.Entry<String, AtomicInteger> entry : httpTypes.entrySet()) {
builder.field(entry.getKey(), entry.getValue().get());
}
builder.endObject();
return builder;
}
}
}