SOLR-15019: Replica placement API needs a way to fetch existing replica metrics.

This commit is contained in:
Andrzej Bialecki 2021-01-04 15:24:25 +01:00
parent dc99e39d21
commit 2695624a9f
26 changed files with 1216 additions and 388 deletions

View File

@ -21,6 +21,10 @@ New Features
Improve support for arbitrary container-level plugins. Add ClusterSingleton
support for plugins that require only one active instance in the cluster. (ab, noble)
* SOLR-14613: Autoscaling replacement using placement plugins (ilan, ab, noble)
* SOLR-15019: Replica placement API needs a way to fetch existing replica metrics. (ab, ilan)
Improvements
----------------------
* LUCENE-8984: MoreLikeThis MLT is biased for uncommon fields (Andy Hind via Anshum Gupta)

View File

@ -18,7 +18,6 @@
package org.apache.solr.cluster;
import org.apache.solr.cluster.placement.AttributeFetcher;
import org.apache.solr.cluster.placement.AttributeValues;
import org.apache.solr.cluster.placement.PlacementPlugin;
import org.apache.solr.cluster.placement.PlacementRequest;
@ -69,12 +68,11 @@ public interface SolrCollection {
* <p>Using custom properties in conjunction with ad hoc {@link PlacementPlugin} code allows customizing placement
* decisions per collection.
*
* <p>For example if a collection is to be placed only on nodes using SSD storage and not rotating disks, it can be
* identified as such using some custom property (collection property could for example be called "driveType" and have
* value "ssd" in that case), and the placement plugin (implementing {@link PlacementPlugin}) would then
* <p>For example if a collection is to be placed only on nodes using located in a specific availability zone, it can be
* identified as such using some custom property (collection property could for example be called "availabilityZone" and have
* value "az1" in that case), and the placement plugin (implementing {@link PlacementPlugin}) would then
* {@link AttributeFetcher#requestNodeSystemProperty(String)} for that property from all nodes and only place replicas
* of this collection on {@link Node}'s for which
* {@link AttributeValues#getDiskType(Node)} is non empty and equal to {@link org.apache.solr.cluster.placement.AttributeFetcher.DiskHardwareType#SSD}.
* of this collection on {@link Node}'s for which this attribute is non empty and equal.
*/
String getCustomProperty(String customPropertyName);

View File

@ -18,6 +18,7 @@
package org.apache.solr.cluster.placement;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.SolrCollection;
import java.util.Set;
@ -25,86 +26,45 @@ import java.util.Set;
* <p>Instances of this interface are used to fetch various attributes from nodes (and other sources) in the cluster.</p>
*/
public interface AttributeFetcher {
/**
* Request the number of cores on each node. To get the value use {@link AttributeValues#getCoresCount(Node)}
*/
AttributeFetcher requestNodeCoreCount();
/**
* Request the disk hardware type on each node. To get the value use {@link AttributeValues#getDiskType(Node)}
*/
AttributeFetcher requestNodeDiskType();
/**
* Request the free disk size on each node. To get the value use {@link AttributeValues#getFreeDisk(Node)}
*/
AttributeFetcher requestNodeFreeDisk();
/**
* Request the total disk size on each node. To get the value use {@link AttributeValues#getTotalDisk(Node)}
*/
AttributeFetcher requestNodeTotalDisk();
/**
* Request the heap usage on each node. To get the value use {@link AttributeValues#getHeapUsage(Node)}
*/
AttributeFetcher requestNodeHeapUsage();
/**
* Request the system load average on each node. To get the value use {@link AttributeValues#getSystemLoadAverage(Node)}
*/
AttributeFetcher requestNodeSystemLoadAverage();
/**
* Request a given system property on each node. To get the value use {@link AttributeValues#getSystemProperty(Node, String)}
* @param name system property name
*/
AttributeFetcher requestNodeSystemProperty(String name);
/**
* Request an environment variable on each node. To get the value use {@link AttributeValues#getEnvironmentVariable(Node, String)}
* @param name environment property name
*/
AttributeFetcher requestNodeEnvironmentVariable(String name);
/**
* Request a node metric from each node. To get the value use {@link AttributeValues#getMetric(Node, String, NodeMetricRegistry)}
* Request a node metric from each node. To get the value use {@link AttributeValues#getNodeMetric(Node, NodeMetric)}
* @param metric metric to retrieve (see {@link NodeMetric})
*/
AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry);
AttributeFetcher requestNodeMetric(NodeMetric<?> metric);
/**
* Request collection-level metrics. To get the values use {@link AttributeValues#getCollectionMetrics(String)}.
* Note that this request will fetch information from nodes that are relevant to the collection
* replicas and not the ones specified in {@link #fetchFrom(Set)} (though they may overlap).
* @param solrCollection request metrics for this collection
* @param metrics metrics to retrieve (see {@link ReplicaMetric})
*/
AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metrics);
/**
* The set of nodes from which to fetch all node related attributes. Calling this method is mandatory if any of the {@code requestNode*}
* methods got called.
* @param nodes nodes to fetch from
*/
AttributeFetcher fetchFrom(Set<Node> nodes);
/**
* Requests a (non node) metric of a given scope and name. To get the value use {@link AttributeValues#getMetric(String, String)}
*/
AttributeFetcher requestMetric(String scope, String metricName);
/**
* Fetches all requested node attributes from all nodes passed to {@link #fetchFrom(Set)} as well as non node attributes
* (those requested for example using {@link #requestMetric(String, String)}.
* Fetches all requested node attributes from all nodes passed to {@link #fetchFrom(Set)} as well as non-node attributes
* (those requested using e.g. {@link #requestCollectionMetrics(SolrCollection, Set)}.
*
* @return An instance allowing retrieval of all attributed that could be fetched.
* @return An instance allowing retrieval of all attributes that could be fetched.
*/
AttributeValues fetchAttributes();
/**
* Registry options for {@link Node} metrics.
*/
enum NodeMetricRegistry {
/**
* corresponds to solr.node
*/
SOLR_NODE,
/**
* corresponds to solr.jvm
*/
SOLR_JVM
}
enum DiskHardwareType {
SSD, ROTATIONAL
}
}

View File

@ -22,36 +22,6 @@ import org.apache.solr.cluster.Node;
import java.util.Optional;
public interface AttributeValues {
/**
* For the given node: number of cores
*/
Optional<Integer> getCoresCount(Node node);
/**
* For the given node: Hardware type of the disk partition where cores are stored
*/
Optional<AttributeFetcher.DiskHardwareType> getDiskType(Node node);
/**
* For the given node: Free disk size in Gigabytes of the partition on which cores are stored
*/
Optional<Long> getFreeDisk(Node node);
/**
* For the given node: Total disk size in Gigabytes of the partition on which cores are stored
*/
Optional<Long> getTotalDisk(Node node);
/**
* For the given node: Percentage between 0 and 100 of used heap over max heap
*/
Optional<Double> getHeapUsage(Node node);
/**
* For the given node: matches {@link java.lang.management.OperatingSystemMXBean#getSystemLoadAverage()}
*/
Optional<Double> getSystemLoadAverage(Node node);
/**
* For the given node: system property value (system properties are passed to Java using {@code -Dname=value}
*/
@ -63,13 +33,12 @@ public interface AttributeValues {
Optional<String> getEnvironmentVariable(Node node, String name);
/**
* For the given node: metric of specific name and registry
* For the given node: metric identified by an instance of {@link NodeMetric}
*/
Optional<Double> getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry);
<T> Optional<T> getNodeMetric(Node node, NodeMetric<T> metric);
/**
* Get a non node related metric of specific scope and name
* Get collection metrics.
*/
Optional<Double> getMetric(String scope, String metricName);
Optional<CollectionMetrics> getCollectionMetrics(String collectionName);
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
import java.util.Iterator;
import java.util.Optional;
/**
* Collection-level metrics. Currently this class is a container just
* for shard-level metrics but future versions may add other
* primitive collection-level metrics.
*/
public interface CollectionMetrics {
Optional<ShardMetrics> getShardMetrics(String shardName);
Iterator<ShardMetrics> iterator();
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
/**
* Metric-related attribute of a node or replica. It defines a short symbolic name of the metric, the corresponding
* internal metric name and the desired format/unit conversion. Generic type
* specifies the type of converted values of this attribute.
*/
public interface Metric<T> {
/**
* Return the short-hand name that identifies this attribute.
*/
String getName();
/**
* Return the internal name of a Solr metric associated with this attribute.
*/
String getInternalName();
/**
* Convert raw value. This may involve changing raw value type or units.
* @param value raw value
* @return converted value
*/
T convert(Object value);
}

View File

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
/**
* Node metric identifier, corresponding
* to a node-level metric registry and the internal metric name.
*/
public interface NodeMetric<T> extends Metric<T> {
/**
* Metric registry. If this metric identifier uses a fully-qualified
* metric key instead, then this method will return {@link Registry#UNSPECIFIED}.
*/
Registry getRegistry();
/**
* Registry options for node metrics.
*/
enum Registry {
/**
* corresponds to solr.node
*/
SOLR_NODE,
/**
* corresponds to solr.jvm
*/
SOLR_JVM,
/**
* corresponds to solr.jetty
*/
SOLR_JETTY,
/**
* In case when the registry name is not relevant (eg. a fully-qualified
* metric key was provided as the metric name).
*/
UNSPECIFIED
}
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
/**
* Replica metric identifier, corresponding to one of the
* internal replica-level metric names (as reported in <code>solr.core.[collection].[replica]</code> registry)
*/
public interface ReplicaMetric<T> extends Metric<T> {
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
/**
* Strongly-typed replica-level metrics.
*/
public interface ReplicaMetrics {
String getReplicaName();
<T> Optional<T> getReplicaMetric(ReplicaMetric<T> metric);
Iterator<Map.Entry<ReplicaMetric<?>, Object>> iterator();
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
import java.util.Iterator;
import java.util.Optional;
/**
* Shard-level metrics. Currently this is just a container for
* replica-level metrics but future versions may add other
* primitive shard-level metrics.
*/
public interface ShardMetrics {
String getShardName();
Optional<ReplicaMetrics> getLeaderMetrics();
Optional<ReplicaMetrics> getReplicaMetrics(String replicaName);
Iterator<ReplicaMetrics> iterator();
}

View File

@ -17,13 +17,17 @@
package org.apache.solr.cluster.placement.impl;
import com.google.common.collect.Maps;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.impl.SolrClientNodeStateProvider;
import org.apache.solr.cluster.SolrCollection;
import org.apache.solr.cluster.placement.AttributeFetcher;
import org.apache.solr.cluster.placement.AttributeValues;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.placement.CollectionMetrics;
import org.apache.solr.cluster.placement.NodeMetric;
import org.apache.solr.cluster.placement.ReplicaMetric;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.metrics.SolrMetricManager;
@ -33,18 +37,18 @@ import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Implementation of {@link AttributeFetcher} that uses {@link SolrCloudManager}
* to access Solr cluster details.
*/
public class AttributeFetcherImpl implements AttributeFetcher {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
boolean requestedNodeCoreCount;
boolean requestedNodeDiskType;
boolean requestedNodeFreeDisk;
boolean requestedNodeTotalDisk;
boolean requestedNodeHeapUsage;
boolean requestedNodeSystemLoadAverage;
Set<String> requestedNodeSystemPropertiesSnitchTags = new HashSet<>();
Set<String> requestedNodeMetricSnitchTags = new HashSet<>();
Set<String> requestedNodeSystemSnitchTags = new HashSet<>();
Set<NodeMetric<?>> requestedNodeMetricSnitchTags = new HashSet<>();
Map<SolrCollection, Set<ReplicaMetric<?>>> requestedCollectionMetrics = new HashMap<>();
Set<Node> nodes = Collections.emptySet();
@ -54,56 +58,29 @@ public class AttributeFetcherImpl implements AttributeFetcher {
this.cloudManager = cloudManager;
}
@Override
public AttributeFetcher requestNodeCoreCount() {
requestedNodeCoreCount = true;
return this;
}
@Override
public AttributeFetcher requestNodeDiskType() {
requestedNodeDiskType = true;
return this;
}
@Override
public AttributeFetcher requestNodeFreeDisk() {
requestedNodeFreeDisk = true;
return this;
}
@Override
public AttributeFetcher requestNodeTotalDisk() {
requestedNodeTotalDisk = true;
return this;
}
@Override
public AttributeFetcher requestNodeHeapUsage() {
requestedNodeHeapUsage = true;
return this;
}
@Override
public AttributeFetcher requestNodeSystemLoadAverage() {
requestedNodeSystemLoadAverage = true;
return this;
}
@Override
public AttributeFetcher requestNodeSystemProperty(String name) {
requestedNodeSystemPropertiesSnitchTags.add(getSystemPropertySnitchTag(name));
requestedNodeSystemSnitchTags.add(getSystemPropertySnitchTag(name));
return this;
}
@Override
public AttributeFetcher requestNodeEnvironmentVariable(String name) {
throw new UnsupportedOperationException("Not yet implemented...");
requestedNodeSystemSnitchTags.add(getSystemEnvSnitchTag(name));
return this;
}
@Override
public AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry) {
requestedNodeMetricSnitchTags.add(getMetricSnitchTag(metricName, registry));
public AttributeFetcher requestNodeMetric(NodeMetric<?> metric) {
requestedNodeMetricSnitchTags.add(metric);
return this;
}
@Override
public AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metrics) {
if (!metrics.isEmpty()) {
requestedCollectionMetrics.put(solrCollection, Set.copyOf(metrics));
}
return this;
}
@ -113,74 +90,49 @@ public class AttributeFetcherImpl implements AttributeFetcher {
return this;
}
@Override
public AttributeFetcher requestMetric(String scope, String metricName) {
throw new UnsupportedOperationException("Not yet implemented...");
}
private static final double GB = 1024 * 1024 * 1024;
@Override
public AttributeValues fetchAttributes() {
// TODO Code here only supports node related attributes for now
// Maps in which attribute values will be added
Map<Node, Integer> nodeToCoreCount = Maps.newHashMap();
Map<Node, DiskHardwareType> nodeToDiskType = Maps.newHashMap();
Map<Node, Long> nodeToFreeDisk = Maps.newHashMap();
Map<Node, Long> nodeToTotalDisk = Maps.newHashMap();
Map<Node, Double> nodeToHeapUsage = Maps.newHashMap();
Map<Node, Double> nodeToSystemLoadAverage = Maps.newHashMap();
Map<String, Map<Node, String>> syspropSnitchToNodeToValue = Maps.newHashMap();
Map<String, Map<Node, Double>> metricSnitchToNodeToValue = Maps.newHashMap();
Map<String, Map<Node, String>> systemSnitchToNodeToValue = new HashMap<>();
Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue = new HashMap<>();
Map<String, CollectionMetricsBuilder> collectionMetricsBuilders = new HashMap<>();
Map<Node, Set<String>> nodeToReplicaInternalTags = new HashMap<>();
Map<String, Set<ReplicaMetric<?>>> requestedCollectionNamesMetrics = requestedCollectionMetrics.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().getName(), e -> e.getValue()));
// In order to match the returned values for the various snitches, we need to keep track of where each
// received value goes. Given the target maps are of different types (the maps from Node to whatever defined
// above) we instead pass a function taking two arguments, the node and the (non null) returned value,
// that will cast the value into the appropriate type for the snitch tag and insert it into the appropriate map
// with the node as the key.
Map<String, BiConsumer<Node, Object>> allSnitchTagsToInsertion = Maps.newHashMap();
if (requestedNodeCoreCount) {
allSnitchTagsToInsertion.put(ImplicitSnitch.CORES, (node, value) -> nodeToCoreCount.put(node, ((Number) value).intValue()));
}
if (requestedNodeDiskType) {
allSnitchTagsToInsertion.put(ImplicitSnitch.DISKTYPE, (node, value) -> {
if ("rotational".equals(value)) {
nodeToDiskType.put(node, DiskHardwareType.ROTATIONAL);
} else if ("ssd".equals(value)) {
nodeToDiskType.put(node, DiskHardwareType.SSD);
}
// unknown disk type: insert no value, returned optional will be empty
});
}
if (requestedNodeFreeDisk) {
allSnitchTagsToInsertion.put(SolrClientNodeStateProvider.Variable.FREEDISK.tagName,
// Convert from bytes to GB
(node, value) -> nodeToFreeDisk.put(node, ((Number) value).longValue() / 1024 / 1024 / 1024));
}
if (requestedNodeTotalDisk) {
allSnitchTagsToInsertion.put(SolrClientNodeStateProvider.Variable.TOTALDISK.tagName,
// Convert from bytes to GB
(node, value) -> nodeToTotalDisk.put(node, ((Number) value).longValue() / 1024 / 1024 / 1024));
}
if (requestedNodeHeapUsage) {
allSnitchTagsToInsertion.put(ImplicitSnitch.HEAPUSAGE,
(node, value) -> nodeToHeapUsage.put(node, ((Number) value).doubleValue()));
}
if (requestedNodeSystemLoadAverage) {
allSnitchTagsToInsertion.put(ImplicitSnitch.SYSLOADAVG,
(node, value) -> nodeToSystemLoadAverage.put(node, ((Number) value).doubleValue()));
}
for (String sysPropSnitch : requestedNodeSystemPropertiesSnitchTags) {
final Map<Node, String> sysPropMap = Maps.newHashMap();
syspropSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
Map<String, BiConsumer<Node, Object>> allSnitchTagsToInsertion = new HashMap<>();
for (String sysPropSnitch : requestedNodeSystemSnitchTags) {
final Map<Node, String> sysPropMap = new HashMap<>();
systemSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
allSnitchTagsToInsertion.put(sysPropSnitch, (node, value) -> sysPropMap.put(node, (String) value));
}
for (String metricSnitch : requestedNodeMetricSnitchTags) {
final Map<Node, Double> metricMap = Maps.newHashMap();
metricSnitchToNodeToValue.put(metricSnitch, metricMap);
allSnitchTagsToInsertion.put(metricSnitch, (node, value) -> metricMap.put(node, (Double) value));
for (NodeMetric<?> metric : requestedNodeMetricSnitchTags) {
final Map<Node, Object> metricMap = new HashMap<>();
metricSnitchToNodeToValue.put(metric, metricMap);
String metricSnitch = getMetricSnitchTag(metric);
allSnitchTagsToInsertion.put(metricSnitch, (node, value) -> metricMap.put(node, metric.convert(value)));
}
requestedCollectionMetrics.forEach((collection, tags) -> {
Set<String> collectionTags = tags.stream().map(tag -> tag.getInternalName()).collect(Collectors.toSet());
collection.shards().forEach(shard ->
shard.replicas().forEach(replica -> {
Set<String> perNodeInternalTags = nodeToReplicaInternalTags
.computeIfAbsent(replica.getNode(), n -> new HashSet<>());
perNodeInternalTags.addAll(collectionTags);
}));
});
// Now that we know everything we need to fetch (and where to put it), just do it.
// TODO: we could probably fetch this in parallel - for large clusters this could
// significantly shorten the execution time
for (Node node : nodes) {
Map<String, Object> tagValues = cloudManager.getNodeStateProvider().getNodeValues(node.getName(), allSnitchTagsToInsertion.keySet());
for (Map.Entry<String, Object> e : tagValues.entrySet()) {
@ -197,32 +149,75 @@ public class AttributeFetcherImpl implements AttributeFetcher {
}
}
return new AttributeValuesImpl(nodeToCoreCount,
nodeToDiskType,
nodeToFreeDisk,
nodeToTotalDisk,
nodeToHeapUsage,
nodeToSystemLoadAverage,
syspropSnitchToNodeToValue,
metricSnitchToNodeToValue);
for (Node node : nodeToReplicaInternalTags.keySet()) {
Set<String> tags = nodeToReplicaInternalTags.get(node);
Map<String, Map<String, List<Replica>>> infos = cloudManager.getNodeStateProvider().getReplicaInfo(node.getName(), tags);
infos.entrySet().stream()
.filter(entry -> requestedCollectionNamesMetrics.containsKey(entry.getKey()))
.forEach(entry -> {
CollectionMetricsBuilder collectionMetricsBuilder = collectionMetricsBuilders
.computeIfAbsent(entry.getKey(), c -> new CollectionMetricsBuilder());
entry.getValue().forEach((shardName, replicas) -> {
CollectionMetricsBuilder.ShardMetricsBuilder shardMetricsBuilder =
collectionMetricsBuilder.getShardMetricsBuilders()
.computeIfAbsent(shardName, s -> new CollectionMetricsBuilder.ShardMetricsBuilder(s));
replicas.forEach(replica -> {
CollectionMetricsBuilder.ReplicaMetricsBuilder replicaMetricsBuilder =
shardMetricsBuilder.getReplicaMetricsBuilders()
.computeIfAbsent(replica.getName(), n -> new CollectionMetricsBuilder.ReplicaMetricsBuilder(n));
replicaMetricsBuilder.setLeader(replica.isLeader());
if (replica.isLeader()) {
shardMetricsBuilder.setLeaderMetrics(replicaMetricsBuilder);
}
Set<ReplicaMetric<?>> requestedMetrics = requestedCollectionNamesMetrics.get(replica.getCollection());
requestedMetrics.forEach(metric -> {
replicaMetricsBuilder.addMetric(metric, replica.get(metric.getInternalName()));
});
});
});
});
}
Map<String, CollectionMetrics> collectionMetrics = new HashMap<>();
collectionMetricsBuilders.forEach((name, builder) -> collectionMetrics.put(name, builder.build()));
return new AttributeValuesImpl(systemSnitchToNodeToValue,
metricSnitchToNodeToValue, collectionMetrics);
}
private static SolrInfoBean.Group getGroupFromMetricRegistry(NodeMetricRegistry registry) {
private static SolrInfoBean.Group getGroupFromMetricRegistry(NodeMetric.Registry registry) {
switch (registry) {
case SOLR_JVM:
return SolrInfoBean.Group.jvm;
case SOLR_NODE:
return SolrInfoBean.Group.node;
case SOLR_JETTY:
return SolrInfoBean.Group.jetty;
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unsupported registry value " + registry);
}
}
public static String getMetricSnitchTag(String metricName, NodeMetricRegistry registry) {
return SolrClientNodeStateProvider.METRICS_PREFIX + SolrMetricManager.getRegistryName(getGroupFromMetricRegistry(registry), metricName);
public static String getMetricSnitchTag(NodeMetric<?> metric) {
if (metric.getRegistry() != NodeMetric.Registry.UNSPECIFIED) {
// regular registry + metricName
return SolrClientNodeStateProvider.METRICS_PREFIX +
SolrMetricManager.getRegistryName(getGroupFromMetricRegistry(metric.getRegistry())) + ":" + metric.getInternalName();
} else if (ImplicitSnitch.tags.contains(metric.getInternalName())) {
// "special" well-known tag
return metric.getInternalName();
} else {
// a fully-qualified metric key
return SolrClientNodeStateProvider.METRICS_PREFIX + metric.getInternalName();
}
}
public static String getSystemPropertySnitchTag(String name) {
return ImplicitSnitch.SYSPROP + name;
}
public static String getSystemEnvSnitchTag(String name) {
return ImplicitSnitch.SYSENV + name;
}
}

View File

@ -17,74 +17,36 @@
package org.apache.solr.cluster.placement.impl;
import org.apache.solr.cluster.placement.AttributeFetcher;
import org.apache.solr.cluster.placement.AttributeValues;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.placement.CollectionMetrics;
import org.apache.solr.cluster.placement.NodeMetric;
import java.util.Map;
import java.util.Optional;
/**
* Implementation of {@link AttributeValues} used by {@link AttributeFetcherImpl}.
*/
public class AttributeValuesImpl implements AttributeValues {
final Map<Node, Integer> nodeToCoreCount;
final Map<Node, AttributeFetcher.DiskHardwareType> nodeToDiskType;
final Map<Node, Long> nodeToFreeDisk;
final Map<Node, Long> nodeToTotalDisk;
final Map<Node, Double> nodeToHeapUsage;
final Map<Node, Double> nodeToSystemLoadAverage;
final Map<String, Map<Node, String>> syspropSnitchToNodeToValue;
final Map<String, Map<Node, Double>> metricSnitchToNodeToValue;
// sysprop (or sysenv) name / node -> value
final Map<String, Map<Node, String>> systemSnitchToNodeToValue;
// metricName / node -> value
final Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue;
// collection / shard / replica / metricName -> value
final Map<String, CollectionMetrics> collectionMetrics;
public AttributeValuesImpl(Map<Node, Integer> nodeToCoreCount,
Map<Node, AttributeFetcher.DiskHardwareType> nodeToDiskType,
Map<Node, Long> nodeToFreeDisk,
Map<Node, Long> nodeToTotalDisk,
Map<Node, Double> nodeToHeapUsage,
Map<Node, Double> nodeToSystemLoadAverage,
Map<String, Map<Node, String>> syspropSnitchToNodeToValue,
Map<String, Map<Node, Double>> metricSnitchToNodeToValue) {
this.nodeToCoreCount = nodeToCoreCount;
this.nodeToDiskType = nodeToDiskType;
this.nodeToFreeDisk = nodeToFreeDisk;
this.nodeToTotalDisk = nodeToTotalDisk;
this.nodeToHeapUsage = nodeToHeapUsage;
this.nodeToSystemLoadAverage = nodeToSystemLoadAverage;
this.syspropSnitchToNodeToValue = syspropSnitchToNodeToValue;
public AttributeValuesImpl(Map<String, Map<Node, String>> systemSnitchToNodeToValue,
Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue,
Map<String, CollectionMetrics> collectionMetrics) {
this.systemSnitchToNodeToValue = systemSnitchToNodeToValue;
this.metricSnitchToNodeToValue = metricSnitchToNodeToValue;
}
@Override
public Optional<Integer> getCoresCount(Node node) {
return Optional.ofNullable(nodeToCoreCount.get(node));
}
@Override
public Optional<AttributeFetcher.DiskHardwareType> getDiskType(Node node) {
return Optional.ofNullable(nodeToDiskType.get(node));
}
@Override
public Optional<Long> getFreeDisk(Node node) {
return Optional.ofNullable(nodeToFreeDisk.get(node));
}
@Override
public Optional<Long> getTotalDisk(Node node) {
return Optional.ofNullable(nodeToTotalDisk.get(node));
}
@Override
public Optional<Double> getHeapUsage(Node node) {
return Optional.ofNullable(nodeToHeapUsage.get(node));
}
@Override
public Optional<Double> getSystemLoadAverage(Node node) {
return Optional.ofNullable(nodeToSystemLoadAverage.get(node));
this.collectionMetrics = collectionMetrics;
}
@Override
public Optional<String> getSystemProperty(Node node, String name) {
Map<Node, String> nodeToValue = syspropSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
Map<Node, String> nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
if (nodeToValue == null) {
return Optional.empty();
}
@ -93,13 +55,7 @@ public class AttributeValuesImpl implements AttributeValues {
@Override
public Optional<String> getEnvironmentVariable(Node node, String name) {
// TODO implement
return Optional.empty();
}
@Override
public Optional<Double> getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry) {
Map<Node, Double> nodeToValue = metricSnitchToNodeToValue.get(AttributeFetcherImpl.getMetricSnitchTag(metricName, registry));
Map<Node, String> nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemEnvSnitchTag(name));
if (nodeToValue == null) {
return Optional.empty();
}
@ -107,8 +63,17 @@ public class AttributeValuesImpl implements AttributeValues {
}
@Override
public Optional<Double> getMetric(String scope, String metricName) {
// TODO implement
return Optional.empty();
@SuppressWarnings("unchecked")
public <T> Optional<T> getNodeMetric(Node node, NodeMetric<T> metric) {
Map<Node, Object> nodeToValue = metricSnitchToNodeToValue.get(metric);
if (nodeToValue == null) {
return Optional.empty();
}
return Optional.ofNullable((T) nodeToValue.get(node));
}
@Override
public Optional<CollectionMetrics> getCollectionMetrics(String collectionName) {
return Optional.ofNullable(collectionMetrics.get(collectionName));
}
}

View File

@ -0,0 +1,164 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement.impl;
import org.apache.solr.cluster.placement.CollectionMetrics;
import org.apache.solr.cluster.placement.ReplicaMetric;
import org.apache.solr.cluster.placement.ReplicaMetrics;
import org.apache.solr.cluster.placement.ShardMetrics;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
/**
* Builder class for constructing instances of {@link CollectionMetrics}.
*/
public class CollectionMetricsBuilder {
final Map<String, ShardMetricsBuilder> shardMetricsBuilders = new HashMap<>();
public Map<String, ShardMetricsBuilder> getShardMetricsBuilders() {
return shardMetricsBuilders;
}
public CollectionMetrics build() {
final Map<String, ShardMetrics> metricsMap = new HashMap<>();
shardMetricsBuilders.forEach((shard, builder) -> metricsMap.put(shard, builder.build()));
return new CollectionMetrics() {
@Override
public Optional<ShardMetrics> getShardMetrics(String shardName) {
return Optional.ofNullable(metricsMap.get(shardName));
}
@Override
public Iterator<ShardMetrics> iterator() {
return metricsMap.values().iterator();
}
};
}
public static class ShardMetricsBuilder {
final Map<String, ReplicaMetricsBuilder> replicaMetricsBuilders = new HashMap<>();
final String shardName;
ReplicaMetricsBuilder leaderMetricsBuilder;
public ShardMetricsBuilder(String shardName) {
this.shardName = shardName;
}
public Map<String, ReplicaMetricsBuilder> getReplicaMetricsBuilders() {
return replicaMetricsBuilders;
}
public ShardMetricsBuilder setLeaderMetrics(ReplicaMetricsBuilder replicaMetricsBuilder) {
leaderMetricsBuilder = replicaMetricsBuilder;
if (leaderMetricsBuilder != null) {
replicaMetricsBuilders.put(leaderMetricsBuilder.replicaName, leaderMetricsBuilder);
}
return this;
}
public ShardMetrics build() {
final Map<String, ReplicaMetrics> metricsMap = new HashMap<>();
replicaMetricsBuilders.forEach((name, replicaBuilder) -> {
ReplicaMetrics metrics = replicaBuilder.build();
metricsMap.put(name, metrics);
if (replicaBuilder.leader) {
if (leaderMetricsBuilder == null) {
leaderMetricsBuilder = replicaBuilder;
} else if (!leaderMetricsBuilder.replicaName.equals(replicaBuilder.replicaName)) {
throw new RuntimeException("two replicas claim to be the shard leader! existing=" +
leaderMetricsBuilder + " and current " + replicaBuilder);
}
}
});
final ReplicaMetrics finalLeaderMetrics = leaderMetricsBuilder != null ? leaderMetricsBuilder.build() : null;
return new ShardMetrics() {
@Override
public String getShardName() {
return shardName;
}
@Override
public Optional<ReplicaMetrics> getLeaderMetrics() {
return Optional.ofNullable(finalLeaderMetrics);
}
@Override
public Optional<ReplicaMetrics> getReplicaMetrics(String replicaName) {
return Optional.ofNullable(metricsMap.get(replicaName));
}
@Override
public Iterator<ReplicaMetrics> iterator() {
return metricsMap.values().iterator();
}
};
}
}
public static class ReplicaMetricsBuilder {
final Map<ReplicaMetric<?>, Object> metrics = new HashMap<>();
final String replicaName;
boolean leader;
public ReplicaMetricsBuilder(String replicaName) {
this.replicaName = replicaName;
}
public ReplicaMetricsBuilder setLeader(boolean leader) {
this.leader = leader;
return this;
}
/** Add unconverted (raw) values here, this method internally calls
* {@link ReplicaMetric#convert(Object)}.
* @param metric metric to add
* @param value raw (unconverted) metric value
*/
public ReplicaMetricsBuilder addMetric(ReplicaMetric<?> metric, Object value) {
value = metric.convert(value);
if (value != null) {
metrics.put(metric, value);
}
return this;
}
public ReplicaMetrics build() {
return new ReplicaMetrics() {
@Override
public String getReplicaName() {
return replicaName;
}
@Override
@SuppressWarnings("unchecked")
public <T> Optional<T> getReplicaMetric(ReplicaMetric<T> metric) {
return Optional.ofNullable((T) metrics.get(metric));
}
@Override
public Iterator<Map.Entry<ReplicaMetric<?>, Object>> iterator() {
return metrics.entrySet().iterator();
}
};
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement.impl;
import org.apache.solr.cluster.placement.Metric;
import java.util.Objects;
import java.util.function.Function;
/**
* Base class for {@link Metric} implementations.
*/
public abstract class MetricImpl<T> implements Metric<T> {
public static final double GB = 1024 * 1024 * 1024;
/**
* Identity converter. It returns the raw value unchanged IFF
* the value's type can be cast to the generic type of this attribute,
* otherwise it returns null.
*/
@SuppressWarnings("unchecked")
public final Function<Object, T> IDENTITY_CONVERTER = v -> {
try {
return (T) v;
} catch (ClassCastException cce) {
return null;
}
};
/**
* Bytes to gigabytes converter. Supports converting number or string
* representations of raw values expressed in bytes.
*/
@SuppressWarnings("unchecked")
public static final Function<Object, Double> BYTES_TO_GB_CONVERTER = v -> {
double sizeInBytes;
if (!(v instanceof Number)) {
if (v == null) {
return null;
}
try {
sizeInBytes = Double.parseDouble(String.valueOf(v));
} catch (Exception nfe) {
return null;
}
} else {
sizeInBytes = ((Number) v).doubleValue();
}
return sizeInBytes / GB;
};
protected final String name;
protected final String internalName;
protected final Function<Object, T> converter;
/**
* Create a metric attribute.
* @param name short-hand name that identifies this attribute.
* @param internalName internal name of a Solr metric.
*/
public MetricImpl(String name, String internalName) {
this(name, internalName, null);
}
/**
* Create a metric attribute.
* @param name short-hand name that identifies this attribute.
* @param internalName internal name of a Solr metric.
* @param converter optional raw value converter. If null then
* {@link #IDENTITY_CONVERTER} will be used.
*/
public MetricImpl(String name, String internalName, Function<Object, T> converter) {
Objects.requireNonNull(name);
Objects.requireNonNull(internalName);
this.name = name;
this.internalName = internalName;
if (converter == null) {
this.converter = IDENTITY_CONVERTER;
} else {
this.converter = converter;
}
}
@Override
public String getName() {
return name;
}
@Override
public String getInternalName() {
return internalName;
}
@Override
public T convert(Object value) {
return converter.apply(value);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MetricImpl<?> that = (MetricImpl<?>) o;
return name.equals(that.getName()) && internalName.equals(that.getInternalName()) && converter.equals(that.converter);
}
@Override
public int hashCode() {
return Objects.hash(name, internalName, converter);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"name=" + name +
", internalName=" + internalName +
"}";
}
}

View File

@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement.impl;
import org.apache.solr.cluster.placement.NodeMetric;
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
import java.util.Objects;
import java.util.function.Function;
/**
* Node metric identifier, corresponding
* to a node-level metric registry and the internal metric name.
*/
public class NodeMetricImpl<T> extends MetricImpl<T> implements NodeMetric<T> {
/** Total disk space in GB. */
public static final NodeMetricImpl<Double> TOTAL_DISK_GB = new NodeMetricImpl<>("totalDisk",
Registry.SOLR_NODE, "CONTAINER.fs.totalSpace", BYTES_TO_GB_CONVERTER);
/** Free (usable) disk space in GB. */
public static final NodeMetricImpl<Double> FREE_DISK_GB = new NodeMetricImpl<>("freeDisk",
Registry.SOLR_NODE, "CONTAINER.fs.usableSpace", BYTES_TO_GB_CONVERTER);
/** Number of all cores. */
public static final NodeMetricImpl<Integer> NUM_CORES = new NodeMetricImpl<>(ImplicitSnitch.CORES);
public static final NodeMetricImpl<Double> HEAP_USAGE = new NodeMetricImpl<>(ImplicitSnitch.HEAPUSAGE);
/** System load average. */
public static final NodeMetricImpl<Double> SYSLOAD_AVG =
new NodeMetricImpl<>("sysLoadAvg", Registry.SOLR_JVM, "os.systemLoadAverage");
/** Number of available processors. */
public static final NodeMetricImpl<Integer> AVAILABLE_PROCESSORS =
new NodeMetricImpl<>("availableProcessors", Registry.SOLR_JVM, "os.availableProcessors");
private final Registry registry;
public NodeMetricImpl(String name, Registry registry, String internalName) {
this(name, registry, internalName, null);
}
public NodeMetricImpl(String name, Registry registry, String internalName, Function<Object, T> converter) {
super(name, internalName, converter);
Objects.requireNonNull(registry);
this.registry = registry;
}
public NodeMetricImpl(String key) {
this(key, null);
}
public NodeMetricImpl(String key, Function<Object, T> converter) {
super(key, key, converter);
this.registry = Registry.UNSPECIFIED;
}
public Registry getRegistry() {
return registry;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
NodeMetricImpl<?> that = (NodeMetricImpl<?>) o;
return registry == that.registry;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), registry);
}
@Override
public String toString() {
if (registry != null) {
return "NodeMetricImpl{" +
"name='" + name + '\'' +
", internalName='" + internalName + '\'' +
", converter=" + converter +
", registry=" + registry +
'}';
} else {
return "NodeMetricImpl{key=" + internalName + "}";
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement.impl;
import org.apache.solr.cluster.placement.ReplicaMetric;
import java.util.function.Function;
/**
* Replica metric identifier, corresponding to one of the
* internal replica-level metric names (as reported in <code>solr.core.[collection].[replica]</code> registry)
*/
public class ReplicaMetricImpl<T> extends MetricImpl<T> implements ReplicaMetric<T> {
public static final ReplicaMetricImpl<Double> INDEX_SIZE_GB = new ReplicaMetricImpl<>("sizeGB", "INDEX.sizeInBytes", BYTES_TO_GB_CONVERTER);
public static final ReplicaMetricImpl<Double> QUERY_RATE_1MIN = new ReplicaMetricImpl<>("queryRate", "QUERY./select.requestTimes:1minRate");
public static final ReplicaMetricImpl<Double> UPDATE_RATE_1MIN = new ReplicaMetricImpl<>("updateRate", "UPDATE./update.requestTimes:1minRate");
public ReplicaMetricImpl(String name, String internalName) {
super(name, internalName);
}
public ReplicaMetricImpl(String name, String internalName, Function<Object, T> converter) {
super(name, internalName, converter);
}
}

View File

@ -21,6 +21,7 @@ import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import org.apache.solr.cluster.*;
import org.apache.solr.cluster.placement.*;
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.SuppressForbidden;
import org.slf4j.Logger;
@ -194,7 +195,9 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
// Request all needed attributes
attributeFetcher.requestNodeSystemProperty(AVAILABILITY_ZONE_SYSPROP).requestNodeSystemProperty(REPLICA_TYPE_SYSPROP);
attributeFetcher.requestNodeCoreCount().requestNodeFreeDisk();
attributeFetcher
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB);
attributeFetcher.fetchFrom(nodes);
final AttributeValues attrValues = attributeFetcher.fetchAttributes();
@ -306,21 +309,21 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
for (Node node : nodes) {
// Exclude nodes with unknown or too small disk free space
if (attrValues.getFreeDisk(node).isEmpty()) {
if (attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("Unknown free disk on node {}, excluding it from placement decisions.", node.getName());
}
// We rely later on the fact that the free disk optional is present (see CoresAndDiskComparator), be careful it you change anything here.
continue;
}
if (attrValues.getFreeDisk(node).get() < minimalFreeDiskGB) {
if (attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).get() < minimalFreeDiskGB) {
if (log.isWarnEnabled()) {
log.warn("Node {} free disk ({}GB) lower than configured minimum {}GB, excluding it from placement decisions.", node.getName(), attrValues.getFreeDisk(node).get(), minimalFreeDiskGB);
log.warn("Node {} free disk ({}GB) lower than configured minimum {}GB, excluding it from placement decisions.", node.getName(), attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).get(), minimalFreeDiskGB);
}
continue;
}
if (attrValues.getCoresCount(node).isEmpty()) {
if (attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("Unknown number of cores on node {}, excluding it from placement decisions.", node.getName());
}
@ -328,7 +331,7 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
continue;
}
Integer coresCount = attrValues.getCoresCount(node).get();
Integer coresCount = attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).get();
coresOnNodes.put(node, coresCount);
String supportedReplicaTypes = attrValues.getSystemProperty(node, REPLICA_TYPE_SYSPROP).isPresent() ? attrValues.getSystemProperty(node, REPLICA_TYPE_SYSPROP).get() : null;
@ -552,8 +555,8 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
@Override
public int compare(Node a, Node b) {
// Note all nodes do have free disk defined. This has been verified earlier.
boolean aHasLowFreeSpace = attrValues.getFreeDisk(a).get() < prioritizedFreeDiskGB;
boolean bHasLowFreeSpace = attrValues.getFreeDisk(b).get() < prioritizedFreeDiskGB;
boolean aHasLowFreeSpace = attrValues.getNodeMetric(a, NodeMetricImpl.FREE_DISK_GB).get() < prioritizedFreeDiskGB;
boolean bHasLowFreeSpace = attrValues.getNodeMetric(b, NodeMetricImpl.FREE_DISK_GB).get() < prioritizedFreeDiskGB;
if (aHasLowFreeSpace != bHasLowFreeSpace) {
// A node with low free space should be considered > node with high free space since it needs to come later in sort order
return Boolean.compare(aHasLowFreeSpace, bHasLowFreeSpace);

View File

@ -31,6 +31,7 @@ import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.Replica;
import org.apache.solr.cluster.SolrCollection;
import org.apache.solr.cluster.placement.*;
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
import org.apache.solr.common.util.SuppressForbidden;
/**
@ -66,17 +67,17 @@ public class MinimizeCoresPlacementFactory implements PlacementPluginFactory<Pla
Set<Node> nodes = request.getTargetNodes();
attributeFetcher.requestNodeCoreCount();
attributeFetcher.requestNodeMetric(NodeMetricImpl.NUM_CORES);
attributeFetcher.fetchFrom(nodes);
AttributeValues attrValues = attributeFetcher.fetchAttributes();
// Get the number of cores on each node and sort the nodes by increasing number of cores
for (Node node : nodes) {
if (attrValues.getCoresCount(node).isEmpty()) {
if (attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).isEmpty()) {
throw new PlacementException("Can't get number of cores in " + node);
}
nodesByCores.put(attrValues.getCoresCount(node).get(), node);
nodesByCores.put(attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).get(), node);
}
Set<ReplicaPlacement> replicaPlacements = new HashSet<>(totalReplicasPerShard * request.getShardNames().size());

View File

@ -244,6 +244,14 @@ public class SolrDispatchFilter extends BaseSolrFilter {
});
});
metricManager.registerGauge(null, registryName, sysprops, metricTag, SolrMetricManager.ResolutionStrategy.IGNORE, "properties", "system");
MetricsMap sysenv = new MetricsMap(map -> {
System.getenv().forEach((k, v) -> {
if (!hiddenSysProps.contains(k)) {
map.putNoEx(String.valueOf(k), v);
}
});
});
metricManager.registerGauge(null, registryName, sysenv, metricTag, SolrMetricManager.ResolutionStrategy.IGNORE, "env", "system");
} catch (Exception e) {
log.warn("Error registering JVM metrics", e);
}

View File

@ -18,8 +18,7 @@
package org.apache.solr.cluster.placement;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.placement.AttributeFetcher;
import org.apache.solr.cluster.placement.AttributeValues;
import org.apache.solr.cluster.SolrCollection;
import java.util.Set;
@ -31,36 +30,6 @@ public class AttributeFetcherForTest implements AttributeFetcher {
this.attributeValues = attributeValues;
}
@Override
public AttributeFetcher requestNodeCoreCount() {
return this;
}
@Override
public AttributeFetcher requestNodeDiskType() {
return this;
}
@Override
public AttributeFetcher requestNodeFreeDisk() {
return this;
}
@Override
public AttributeFetcher requestNodeTotalDisk() {
return this;
}
@Override
public AttributeFetcher requestNodeHeapUsage() {
return this;
}
@Override
public AttributeFetcher requestNodeSystemLoadAverage() {
return this;
}
@Override
public AttributeFetcher requestNodeSystemProperty(String name) {
return this;
@ -72,7 +41,12 @@ public class AttributeFetcherForTest implements AttributeFetcher {
}
@Override
public AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry) {
public AttributeFetcher requestNodeMetric(NodeMetric<?> metric) {
return this;
}
@Override
public AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metricNames) {
return this;
}
@ -81,11 +55,6 @@ public class AttributeFetcherForTest implements AttributeFetcher {
return this;
}
@Override
public AttributeFetcher requestMetric(String scope, String metricName) {
throw new UnsupportedOperationException("Not yet implemented...");
}
@Override
public AttributeValues fetchAttributes() {
return attributeValues;

View File

@ -20,6 +20,9 @@ package org.apache.solr.cluster.placement;
import org.apache.solr.cluster.*;
import org.apache.solr.cluster.placement.impl.AttributeFetcherImpl;
import org.apache.solr.cluster.placement.impl.AttributeValuesImpl;
import org.apache.solr.cluster.placement.impl.CollectionMetricsBuilder;
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
import org.apache.solr.cluster.placement.impl.ReplicaMetricImpl;
import org.apache.solr.common.util.Pair;
import org.junit.Assert;
@ -48,7 +51,10 @@ public class Builders {
public ClusterBuilder initializeLiveNodes(int countNodes) {
nodeBuilders = new LinkedList<>();
for (int n = 0; n < countNodes; n++) {
nodeBuilders.add(new NodeBuilder().setNodeName("node_" + n)); // Default name, can be changed
NodeBuilder nodeBuilder = new NodeBuilder().setNodeName("node_" + n); // Default name, can be changed
nodeBuilder.setTotalDiskGB(10000.0);
nodeBuilder.setFreeDiskGB(5000.0);
nodeBuilders.add(nodeBuilder);
}
return this;
}
@ -87,10 +93,9 @@ public class Builders {
}
public AttributeFetcher buildAttributeFetcher() {
Map<Node, Integer> nodeToCoreCount = new HashMap<>();
Map<Node, Long> nodeToFreeDisk = new HashMap<>();
Map<String, Map<Node, String>> sysprops = new HashMap<>();
Map<String, Map<Node, Double>> metrics = new HashMap<>();
Map<NodeMetric<?>, Map<Node, Object>> metrics = new HashMap<>();
Map<String, CollectionMetrics> collectionMetrics = new HashMap<>();
// TODO And a few more missing and will be added...
@ -100,10 +105,16 @@ public class Builders {
Node node = nodeBuilder.build();
if (nodeBuilder.getCoreCount() != null) {
nodeToCoreCount.put(node, nodeBuilder.getCoreCount());
metrics.computeIfAbsent(NodeMetricImpl.NUM_CORES, n -> new HashMap<>())
.put(node, nodeBuilder.getCoreCount());
}
if (nodeBuilder.getFreeDiskGB() != null) {
nodeToFreeDisk.put(node, nodeBuilder.getFreeDiskGB());
metrics.computeIfAbsent(NodeMetricImpl.FREE_DISK_GB, n -> new HashMap<>())
.put(node, nodeBuilder.getFreeDiskGB());
}
if (nodeBuilder.getTotalDiskGB() != null) {
metrics.computeIfAbsent(NodeMetricImpl.TOTAL_DISK_GB, n -> new HashMap<>())
.put(node, nodeBuilder.getTotalDiskGB());
}
if (nodeBuilder.getSysprops() != null) {
nodeBuilder.getSysprops().forEach((name, value) -> {
@ -119,7 +130,20 @@ public class Builders {
}
}
AttributeValues attributeValues = new AttributeValuesImpl(nodeToCoreCount, Map.of(), nodeToFreeDisk, Map.of(), Map.of(), Map.of(), sysprops, metrics);
if (!collectionBuilders.isEmpty()) {
Map<Node, Object> nodeToCoreCount = metrics.computeIfAbsent(NodeMetricImpl.NUM_CORES, n -> new HashMap<>());
collectionBuilders.forEach(builder -> {
collectionMetrics.put(builder.collectionName, builder.collectionMetricsBuilder.build());
SolrCollection collection = builder.build();
collection.iterator().forEachRemaining(shard ->
shard.iterator().forEachRemaining(replica -> {
nodeToCoreCount.compute(replica.getNode(), (node, count) ->
(count == null) ? 1 : ((Number) count).intValue() + 1);
}));
});
}
AttributeValues attributeValues = new AttributeValuesImpl(sysprops, metrics, collectionMetrics);
return new AttributeFetcherForTest(attributeValues);
}
}
@ -129,6 +153,7 @@ public class Builders {
private LinkedList<ShardBuilder> shardBuilders = new LinkedList<>();
private Map<String, String> customProperties = new HashMap<>();
int replicaNumber = 0; // global replica numbering for the collection
private CollectionMetricsBuilder collectionMetricsBuilder = new CollectionMetricsBuilder();
public CollectionBuilder(String collectionName) {
this.collectionName = collectionName;
@ -139,6 +164,10 @@ public class Builders {
return this;
}
public CollectionMetricsBuilder getCollectionMetricsBuilder() {
return collectionMetricsBuilder;
}
/**
* @return The internal shards data structure to allow test code to modify the replica distribution to nodes.
*/
@ -222,18 +251,46 @@ public class Builders {
* Initializes shard and replica builders for the collection based on passed parameters. Replicas are assigned round
* robin to the nodes. The shard leader is the first NRT replica of each shard (or first TLOG is no NRT).
* Shard and replica configuration can be modified afterwards, the returned builder hierarchy is a convenient starting point.
* @param countShards number of shards to create
* @param countNrtReplicas number of NRT replicas per shard
* @param countTlogReplicas number of TLOG replicas per shard
* @param countPullReplicas number of PULL replicas per shard
* @param nodes list of nodes to place replicas on.
*/
public CollectionBuilder initializeShardsReplicas(int countShards, int countNrtReplicas, int countTlogReplicas,
int countPullReplicas, List<NodeBuilder> nodes) {
int countPullReplicas, List<NodeBuilder> nodes) {
return initializeShardsReplicas(countShards, countNrtReplicas, countTlogReplicas, countPullReplicas, nodes, null);
}
/**
* Initializes shard and replica builders for the collection based on passed parameters. Replicas are assigned round
* robin to the nodes. The shard leader is the first NRT replica of each shard (or first TLOG is no NRT).
* Shard and replica configuration can be modified afterwards, the returned builder hierarchy is a convenient starting point.
* @param countShards number of shards to create
* @param countNrtReplicas number of NRT replicas per shard
* @param countTlogReplicas number of TLOG replicas per shard
* @param countPullReplicas number of PULL replicas per shard
* @param nodes list of nodes to place replicas on.
* @param initialSizeGBPerShard initial replica size (in GB) per shard
*/
public CollectionBuilder initializeShardsReplicas(int countShards, int countNrtReplicas, int countTlogReplicas,
int countPullReplicas, List<NodeBuilder> nodes,
List<Integer> initialSizeGBPerShard) {
Iterator<NodeBuilder> nodeIterator = nodes.iterator();
shardBuilders = new LinkedList<>();
if (initialSizeGBPerShard != null && initialSizeGBPerShard.size() != countShards) {
throw new RuntimeException("list of shard sizes must be the same length as the countShards!");
}
for (int shardNumber = 1; shardNumber <= countShards; shardNumber++) {
String shardName = buildShardName(shardNumber);
CollectionMetricsBuilder.ShardMetricsBuilder shardMetricsBuilder = new CollectionMetricsBuilder.ShardMetricsBuilder(shardName);
LinkedList<ReplicaBuilder> replicas = new LinkedList<>();
ReplicaBuilder leader = null;
CollectionMetricsBuilder.ReplicaMetricsBuilder leaderMetrics = null;
// Iterate on requested counts, NRT then TLOG then PULL. Leader chosen as first NRT (or first TLOG if no NRT)
List<Pair<Replica.ReplicaType, Integer>> replicaTypes = List.of(
@ -258,15 +315,23 @@ public class Builders {
.setReplicaState(Replica.ReplicaState.ACTIVE).setReplicaNode(node);
replicas.add(replicaBuilder);
CollectionMetricsBuilder.ReplicaMetricsBuilder replicaMetricsBuilder = new CollectionMetricsBuilder.ReplicaMetricsBuilder(replicaName);
shardMetricsBuilder.getReplicaMetricsBuilders().put(replicaName, replicaMetricsBuilder);
if (initialSizeGBPerShard != null) {
replicaMetricsBuilder.addMetric(ReplicaMetricImpl.INDEX_SIZE_GB, initialSizeGBPerShard.get(shardNumber - 1) * ReplicaMetricImpl.GB);
}
if (leader == null && type != Replica.ReplicaType.PULL) {
leader = replicaBuilder;
leaderMetrics = replicaMetricsBuilder;
}
}
}
ShardBuilder shardBuilder = new ShardBuilder();
shardBuilder.setShardName(shardName).setReplicaBuilders(replicas).setLeader(leader);
shardMetricsBuilder.setLeaderMetrics(leaderMetrics);
shardBuilders.add(shardBuilder);
collectionMetricsBuilder.getShardMetricsBuilders().put(shardName, shardMetricsBuilder);
}
return this;
@ -353,6 +418,7 @@ public class Builders {
private Replica.ReplicaType replicaType;
private Replica.ReplicaState replicaState;
private NodeBuilder replicaNode;
private Map<ReplicaMetric<?>, Object> metrics;
public ReplicaBuilder setReplicaName(String replicaName) {
this.replicaName = replicaName;
@ -383,6 +449,14 @@ public class Builders {
return this;
}
public ReplicaBuilder setReplicaMetric(ReplicaMetric<?> metric, Object value) {
if (metrics == null) {
metrics = new HashMap<>();
}
metrics.put(metric, metric.convert(value));
return this;
}
public Replica build(Shard shard) {
return new ClusterAbstractionsForTest.ReplicaImpl(replicaName, coreName, shard, replicaType, replicaState, replicaNode.build());
}
@ -391,9 +465,10 @@ public class Builders {
public static class NodeBuilder {
private String nodeName = null;
private Integer coreCount = null;
private Long freeDiskGB = null;
private Double freeDiskGB = null;
private Double totalDiskGB = null;
private Map<String, String> sysprops = null;
private Map<String, Double> metrics = null;
private Map<NodeMetric<?>, Object> metrics = null;
public NodeBuilder setNodeName(String nodeName) {
this.nodeName = nodeName;
@ -405,11 +480,16 @@ public class Builders {
return this;
}
public NodeBuilder setFreeDiskGB(Long freeDiskGB) {
public NodeBuilder setFreeDiskGB(Double freeDiskGB) {
this.freeDiskGB = freeDiskGB;
return this;
}
public NodeBuilder setTotalDiskGB(Double totalDiskGB) {
this.totalDiskGB = totalDiskGB;
return this;
}
public NodeBuilder setSysprop(String key, String value) {
if (sysprops == null) {
sysprops = new HashMap<>();
@ -419,12 +499,11 @@ public class Builders {
return this;
}
public NodeBuilder setMetric(AttributeFetcher.NodeMetricRegistry registry, String key, Double value) {
public NodeBuilder setMetric(NodeMetric<?> metric, Object value) {
if (metrics == null) {
metrics = new HashMap<>();
}
String name = AttributeFetcherImpl.getMetricSnitchTag(key, registry);
metrics.put(name, value);
metrics.put(metric, metric.convert(value));
return this;
}
@ -432,15 +511,19 @@ public class Builders {
return coreCount;
}
public Long getFreeDiskGB() {
public Double getFreeDiskGB() {
return freeDiskGB;
}
public Double getTotalDiskGB() {
return totalDiskGB;
}
public Map<String, String> getSysprops() {
return sysprops;
}
public Map<String, Double> getMetrics() {
public Map<NodeMetric<?>, Object> getMetrics() {
return metrics;
}

View File

@ -0,0 +1,129 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.solr.cluster.placement;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.cluster.Cluster;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.Shard;
import org.apache.solr.cluster.SolrCollection;
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
import org.apache.solr.cluster.placement.impl.ReplicaMetricImpl;
import org.junit.Test;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.apache.solr.cluster.placement.Builders.*;
/**
*
*/
public class BuildersTest extends SolrTestCaseJ4 {
@Test
public void testClusterBuilder() throws Exception {
int NUM_NODES = 3;
int NUM_SHARDS = 2;
int NUM_NRT_REPLICAS = 2;
String collectionName = "test";
ClusterBuilder clusterBuilder = newClusterBuilder()
.initializeLiveNodes(NUM_NODES);
CollectionBuilder collectionBuilder = newCollectionBuilder(collectionName)
.initializeShardsReplicas(NUM_SHARDS,
NUM_NRT_REPLICAS,
NUM_NRT_REPLICAS + 1,
NUM_NRT_REPLICAS + 2,
clusterBuilder.getLiveNodeBuilders(),
List.of(10, 20));
clusterBuilder.addCollection(collectionBuilder);
Cluster cluster = clusterBuilder.build();
assertEquals("number of nodes", NUM_NODES, cluster.getLiveNodes().size());
SolrCollection collection = cluster.getCollection(collectionName);
assertNotNull("collection", collection);
assertEquals("shards", 2, collection.getShardNames().size());
for (String shardName : collection.getShardNames()) {
Shard shard = collection.getShard(shardName);
assertNotNull("shard leader", shard.getLeader());
int[] counts = new int[3];
shard.iterator().forEachRemaining(r -> {
switch (r.getType()) {
case NRT:
counts[0]++;
break;
case TLOG:
counts[1]++;
break;
case PULL:
counts[2]++;
}
});
assertEquals("numNrt", NUM_NRT_REPLICAS, counts[0]);
assertEquals("numTlog", NUM_NRT_REPLICAS + 1, counts[1]);
assertEquals("numPull", NUM_NRT_REPLICAS + 2, counts[2]);
}
// AttributeFetcher
AttributeFetcher attributeFetcher = clusterBuilder.buildAttributeFetcher();
attributeFetcher
.fetchFrom(cluster.getLiveNodes())
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB)
.requestNodeMetric(NodeMetricImpl.TOTAL_DISK_GB)
.requestCollectionMetrics(collection, Set.of(ReplicaMetricImpl.INDEX_SIZE_GB));
AttributeValues attributeValues = attributeFetcher.fetchAttributes();
for (Node node : cluster.getLiveNodes()) {
Optional<Integer> coreCount = attributeValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES);
assertTrue("coreCount present", coreCount.isPresent());
Optional<Double> diskOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB);
assertTrue("freeDisk", diskOpt.isPresent());
diskOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.TOTAL_DISK_GB);
assertTrue("totalDisk", diskOpt.isPresent());
}
Optional<CollectionMetrics> collectionMetricsOpt = attributeValues.getCollectionMetrics(collectionName);
assertTrue("collectionMetrics present", collectionMetricsOpt.isPresent());
CollectionMetrics collectionMetrics = collectionMetricsOpt.get();
for (String shardName : collection.getShardNames()) {
Optional<ShardMetrics> shardMetricsOpt = collectionMetrics.getShardMetrics(shardName);
assertTrue("shard metrics", shardMetricsOpt.isPresent());
ShardMetrics shardMetrics = shardMetricsOpt.get();
Optional<ReplicaMetrics> replicaMetricsOpt = shardMetrics.getLeaderMetrics();
assertTrue("leader metrics", replicaMetricsOpt.isPresent());
ReplicaMetrics leaderMetrics = replicaMetricsOpt.get();
Optional<Double> sizeOpt = leaderMetrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
assertTrue("missing size", sizeOpt.isPresent());
if (shardName.endsWith("1")) {
assertEquals("size", 10, ((Number) sizeOpt.get()).intValue());
} else {
assertEquals("size", 20, ((Number) sizeOpt.get()).intValue());
}
Shard shard = collection.getShard(shardName);
shard.iterator().forEachRemaining(r -> {
Optional<ReplicaMetrics> metricsOpt = shardMetrics.getReplicaMetrics(r.getReplicaName());
assertTrue("replica metrics", metricsOpt.isPresent());
ReplicaMetrics metrics = metricsOpt.get();
Optional<Double> replicaSizeOpt = metrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
assertTrue("missing size", replicaSizeOpt.isPresent());
if (shardName.endsWith("1")) {
assertEquals("size", 10, ((Number) replicaSizeOpt.get()).intValue());
} else {
assertEquals("size", 20, ((Number) replicaSizeOpt.get()).intValue());
}
});
}
}
}

View File

@ -24,8 +24,17 @@ import org.apache.solr.client.solrj.request.beans.PluginMeta;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.V2Response;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.cluster.Cluster;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.SolrCollection;
import org.apache.solr.cluster.placement.AttributeFetcher;
import org.apache.solr.cluster.placement.AttributeValues;
import org.apache.solr.cluster.placement.CollectionMetrics;
import org.apache.solr.cluster.placement.NodeMetric;
import org.apache.solr.cluster.placement.PlacementPluginConfig;
import org.apache.solr.cluster.placement.PlacementPluginFactory;
import org.apache.solr.cluster.placement.ReplicaMetrics;
import org.apache.solr.cluster.placement.ShardMetrics;
import org.apache.solr.cluster.placement.plugins.AffinityPlacementConfig;
import org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory;
import org.apache.solr.cloud.MiniSolrCloudCluster;
@ -47,6 +56,8 @@ import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@ -220,6 +231,80 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
assertNull("no factory should be present", factory);
}
@Test
public void testAttributeFetcherImpl() throws Exception {
CollectionAdminResponse rsp = CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
.process(cluster.getSolrClient());
assertTrue(rsp.isSuccess());
cluster.waitForActiveCollection(COLLECTION, 2, 4);
Cluster cluster = new SimpleClusterAbstractionsImpl.ClusterImpl(cloudManager);
SolrCollection collection = cluster.getCollection(COLLECTION);
AttributeFetcher attributeFetcher = new AttributeFetcherImpl(cloudManager);
NodeMetric<String> someMetricKey = new NodeMetricImpl<>("solr.jvm:system.properties:user.name");
String sysprop = "user.name";
String sysenv = "PWD";
attributeFetcher
.fetchFrom(cluster.getLiveNodes())
.requestNodeMetric(NodeMetricImpl.HEAP_USAGE)
.requestNodeMetric(NodeMetricImpl.SYSLOAD_AVG)
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB)
.requestNodeMetric(NodeMetricImpl.TOTAL_DISK_GB)
.requestNodeMetric(NodeMetricImpl.AVAILABLE_PROCESSORS)
.requestNodeMetric(someMetricKey)
.requestNodeSystemProperty(sysprop)
.requestNodeEnvironmentVariable(sysenv)
.requestCollectionMetrics(collection, Set.of(ReplicaMetricImpl.INDEX_SIZE_GB, ReplicaMetricImpl.QUERY_RATE_1MIN, ReplicaMetricImpl.UPDATE_RATE_1MIN));
AttributeValues attributeValues = attributeFetcher.fetchAttributes();
String userName = System.getProperty("user.name");
String pwd = System.getenv("PWD");
// node metrics
for (Node node : cluster.getLiveNodes()) {
Optional<Double> doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.HEAP_USAGE);
assertTrue("heap usage", doubleOpt.isPresent());
assertTrue("heap usage should be 0 < heapUsage < 100 but was " + doubleOpt, doubleOpt.get() > 0 && doubleOpt.get() < 100);
doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.TOTAL_DISK_GB);
assertTrue("total disk", doubleOpt.isPresent());
assertTrue("total disk should be > 0 but was " + doubleOpt, doubleOpt.get() > 0);
doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB);
assertTrue("free disk", doubleOpt.isPresent());
assertTrue("free disk should be > 0 but was " + doubleOpt, doubleOpt.get() > 0);
Optional<Integer> intOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES);
assertTrue("cores", intOpt.isPresent());
assertTrue("cores should be > 0", intOpt.get() > 0);
assertTrue("systemLoadAverage 2", attributeValues.getNodeMetric(node, NodeMetricImpl.SYSLOAD_AVG).isPresent());
assertTrue("availableProcessors", attributeValues.getNodeMetric(node, NodeMetricImpl.AVAILABLE_PROCESSORS).isPresent());
Optional<String> userNameOpt = attributeValues.getNodeMetric(node, someMetricKey);
assertTrue("user.name", userNameOpt.isPresent());
assertEquals("userName", userName, userNameOpt.get());
Optional<String> syspropOpt = attributeValues.getSystemProperty(node, sysprop);
assertTrue("sysprop", syspropOpt.isPresent());
assertEquals("user.name sysprop", userName, syspropOpt.get());
Optional<String> sysenvOpt = attributeValues.getEnvironmentVariable(node, sysenv);
assertTrue("sysenv", sysenvOpt.isPresent());
assertEquals("PWD sysenv", pwd, sysenvOpt.get());
}
assertTrue(attributeValues.getCollectionMetrics(COLLECTION).isPresent());
CollectionMetrics collectionMetrics = attributeValues.getCollectionMetrics(COLLECTION).get();
collection.shards().forEach(shard -> {
Optional<ShardMetrics> shardMetricsOpt = collectionMetrics.getShardMetrics(shard.getShardName());
assertTrue("shard metrics", shardMetricsOpt.isPresent());
shard.replicas().forEach(replica -> {
Optional<ReplicaMetrics> replicaMetricsOpt = shardMetricsOpt.get().getReplicaMetrics(replica.getReplicaName());
assertTrue("replica metrics", replicaMetricsOpt.isPresent());
ReplicaMetrics replicaMetrics = replicaMetricsOpt.get();
Optional<Double> indexSizeOpt = replicaMetrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
assertTrue("indexSize", indexSizeOpt.isPresent());
assertTrue("wrong type, expected Double but was " + indexSizeOpt.get().getClass(), indexSizeOpt.get() instanceof Double);
assertTrue("indexSize should be > 0 but was " + indexSizeOpt.get(), indexSizeOpt.get() > 0);
assertTrue("indexSize should be < 0.01 but was " + indexSizeOpt.get(), indexSizeOpt.get() < 0.01);
assertNotNull("queryRate", replicaMetrics.getReplicaMetric(ReplicaMetricImpl.QUERY_RATE_1MIN));
assertNotNull("updateRate", replicaMetrics.getReplicaMetric(ReplicaMetricImpl.UPDATE_RATE_1MIN));
});
});
}
private int waitForVersionChange(int currentVersion, DelegatingPlacementPluginFactory wrapper, int timeoutSec) throws Exception {
TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, TimeSource.NANO_TIME);

View File

@ -79,8 +79,8 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(2);
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
nodeBuilders.get(0).setCoreCount(1).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
nodeBuilders.get(1).setCoreCount(10).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
nodeBuilders.get(0).setCoreCount(1).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
nodeBuilders.get(1).setCoreCount(10).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
Builders.CollectionBuilder collectionBuilder = Builders.newCollectionBuilder(collectionName);
@ -126,11 +126,11 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
for (int i = 0; i < nodeBuilders.size(); i++) {
if (i == LOW_SPACE_NODE_INDEX) {
nodeBuilders.get(i).setCoreCount(1).setFreeDiskGB(MINIMAL_FREE_DISK_GB + 1); // Low space
nodeBuilders.get(i).setCoreCount(1).setFreeDiskGB((double)(MINIMAL_FREE_DISK_GB + 1)); // Low space
} else if (i == NO_SPACE_NODE_INDEX) {
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(1L); // Really not enough space
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(1.0); // Really not enough space
} else {
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
}
}
List<Node> liveNodes = clusterBuilder.buildLiveNodes();
@ -190,7 +190,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
int coresOnNode = 10;
for (Builders.NodeBuilder nodeBuilder : nodeBuilders) {
nodeBuilder.setCoreCount(coresOnNode).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
nodeBuilder.setCoreCount(coresOnNode).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
coresOnNode += 10;
}
@ -262,7 +262,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
for (int i = 0; i < 9; i++) {
final String az;
final int numcores;
final long freedisk;
final double freedisk;
final String acceptedReplicaType;
if (i == AZ1_NRT_LOWCORES || i == AZ1_NRT_HIGHCORES || i == AZ1_TLOGPULL_LOWFREEDISK) {
@ -347,7 +347,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
for (int i = 0; i < 9; i++) {
nodeBuilders.get(i).setSysprop(AffinityPlacementFactory.AVAILABILITY_ZONE_SYSPROP, "AZ" + (i / 3))
.setCoreCount(i)
.setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
.setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 10));
}
// The collection does not exist, has 1 shard.
@ -390,7 +390,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
int coreCount = 0;
for (Builders.NodeBuilder nodeBuilder : nodeBuilders) {
nodeBuilder.setCoreCount(coreCount++).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
nodeBuilder.setCoreCount(coreCount++).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
}
// The collection already exists with shards and replicas
@ -493,7 +493,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
for (int i = 0; i < NUM_NODES; i++) {
Builders.NodeBuilder nodeBuilder = clusterBuilder.getLiveNodeBuilders().get(i);
nodeBuilder.setCoreCount(0);
nodeBuilder.setFreeDiskGB(100L);
nodeBuilder.setFreeDiskGB(100.0);
if (i < NUM_NODES / 2) {
nodeBuilder.setSysprop(AffinityPlacementFactory.AVAILABILITY_ZONE_SYSPROP, "az1");
} else {
@ -551,7 +551,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
for (int i = 0; i < NUM_NODES; i++) {
Builders.NodeBuilder nodeBuilder = clusterBuilder.getLiveNodeBuilders().get(i);
nodeBuilder.setCoreCount(0);
nodeBuilder.setFreeDiskGB(100L);
nodeBuilder.setFreeDiskGB(100.0);
if (i < NUM_NODES / 3 * 2) {
nodeBuilder.setSysprop(AffinityPlacementFactory.REPLICA_TYPE_SYSPROP, "Nrt, TlOg");
nodeBuilder.setSysprop("group", "one");
@ -621,10 +621,10 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
nodeBuilder.setCoreCount(0);
if (i == 0) {
// default minimalFreeDiskGB == 20
nodeBuilder.setFreeDiskGB(1L);
nodeBuilder.setFreeDiskGB(1.0);
smallNode = nodeBuilder.build();
} else {
nodeBuilder.setFreeDiskGB(100L);
nodeBuilder.setFreeDiskGB(100.0);
}
}
@ -678,7 +678,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(numNodes);
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
for (int i = 0; i < numNodes; i++) {
nodeBuilders.get(i).setCoreCount(0).setFreeDiskGB(Long.valueOf(numNodes));
nodeBuilders.get(i).setCoreCount(0).setFreeDiskGB((double) numNodes);
}
Builders.CollectionBuilder collectionBuilder = Builders.newCollectionBuilder(collectionName);

View File

@ -63,10 +63,6 @@ import static java.util.Collections.emptyMap;
public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter {
public static final String METRICS_PREFIX = "metrics:";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
//only for debugging
public static SolrClientNodeStateProvider INST;
private final CloudSolrClient solrClient;
protected final Map<String, Map<String, Map<String, List<Replica>>>> nodeVsCollectionVsShardVsReplicaInfo = new HashMap<>();
@ -81,7 +77,6 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
if(log.isDebugEnabled()) INST = this;
}
protected ClusterStateProvider getClusterStateProvider() {
@ -167,28 +162,30 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
}
protected Map<String, Object> fetchReplicaMetrics(String node, Map<String, Pair<String, Replica>> metricsKeyVsTagReplica) {
Map<String, Object> collect = metricsKeyVsTagReplica.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getKey));
Map<String, Set<Object>> collect = metricsKeyVsTagReplica.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> Set.of(e.getKey())));
ClientSnitchCtx ctx = new ClientSnitchCtx(null, null, emptyMap(), solrClient);
fetchReplicaMetrics(node, ctx, collect);
return ctx.getTags();
}
static void fetchReplicaMetrics(String solrNode, ClientSnitchCtx ctx, Map<String, Object> metricsKeyVsTag) {
static void fetchReplicaMetrics(String solrNode, ClientSnitchCtx ctx, Map<String, Set<Object>> metricsKeyVsTag) {
if (!ctx.isNodeAlive(solrNode)) return;
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("key", metricsKeyVsTag.keySet().toArray(new String[0]));
try {
SimpleSolrResponse rsp = ctx.invokeWithRetry(solrNode, CommonParams.METRICS_PATH, params);
metricsKeyVsTag.forEach((key, tag) -> {
metricsKeyVsTag.forEach((key, tags) -> {
Object v = Utils.getObjectByPath(rsp.nl, true, Arrays.asList("metrics", key));
if (tag instanceof Function) {
@SuppressWarnings({"unchecked"})
Pair<String, Object> p = (Pair<String, Object>) ((Function) tag).apply(v);
ctx.getTags().put(p.first(), p.second());
} else {
if (v != null) ctx.getTags().put(tag.toString(), v);
for (Object tag : tags) {
if (tag instanceof Function) {
@SuppressWarnings({"unchecked"})
Pair<String, Object> p = (Pair<String, Object>) ((Function) tag).apply(v);
ctx.getTags().put(p.first(), p.second());
} else {
if (v != null) ctx.getTags().put(tag.toString(), v);
}
}
});
} catch (Exception e) {
@ -207,26 +204,19 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
protected void getRemoteInfo(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
if (!((ClientSnitchCtx)ctx).isNodeAlive(solrNode)) return;
ClientSnitchCtx snitchContext = (ClientSnitchCtx) ctx;
Map<String, Object> metricsKeyVsTag = new HashMap<>();
Map<String, Set<Object>> metricsKeyVsTag = new HashMap<>();
for (String tag : requestedTags) {
if (tag.startsWith(SYSPROP)) {
metricsKeyVsTag.put("solr.jvm:system.properties:" + tag.substring(SYSPROP.length()), tag);
metricsKeyVsTag.computeIfAbsent("solr.jvm:system.properties:" + tag.substring(SYSPROP.length()), k -> new HashSet<>())
.add(tag);
} else if (tag.startsWith(SYSENV)) {
metricsKeyVsTag.computeIfAbsent("solr.jvm:system.env:" + tag.substring(SYSENV.length()), k -> new HashSet<>())
.add(tag);
} else if (tag.startsWith(METRICS_PREFIX)) {
metricsKeyVsTag.put(tag.substring(METRICS_PREFIX.length()), tag);
metricsKeyVsTag.computeIfAbsent(tag.substring(METRICS_PREFIX.length()), k -> new HashSet<>())
.add(tag);
}
}
if (requestedTags.contains(ImplicitSnitch.DISKTYPE)) {
metricsKeyVsTag.put("solr.node:CONTAINER.fs.coreRoot.spins", (Function<Object, Pair<String, Object>>) o -> {
if("true".equals(String.valueOf(o))){
return new Pair<>(ImplicitSnitch.DISKTYPE, "rotational");
}
if("false".equals(String.valueOf(o))){
return new Pair<>(ImplicitSnitch.DISKTYPE, "ssd");
}
return new Pair<>(ImplicitSnitch.DISKTYPE,null);
});
}
if (!metricsKeyVsTag.isEmpty()) {
fetchReplicaMetrics(solrNode, snitchContext, metricsKeyVsTag);
}
@ -283,7 +273,7 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
}
if (requestedTags.contains(SYSLOADAVG)) {
Number n = (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/os.systemLoadAverage");
if (n != null) ctx.getTags().put(SYSLOADAVG, n.doubleValue() * 100.0d);
if (n != null) ctx.getTags().put(SYSLOADAVG, n.doubleValue());
}
if (requestedTags.contains(HEAPUSAGE)) {
Number n = (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/memory.heap.usage");

View File

@ -49,11 +49,11 @@ public class ImplicitSnitch extends Snitch {
public static final String ROLE = "role";
public static final String NODEROLE = "nodeRole";
public static final String SYSPROP = "sysprop.";
public static final String SYSENV = "sysenv.";
public static final String SYSLOADAVG = "sysLoadAvg";
public static final String HEAPUSAGE = "heapUsage";
public static final String DISKTYPE = "diskType";
public static final List<String> IP_SNITCHES = Collections.unmodifiableList(Arrays.asList("ip_1", "ip_2", "ip_3", "ip_4"));
public static final Set<String> tags = Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, "ip_1", "ip_2", "ip_3", "ip_4");
public static final Set<String> tags = Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, HEAPUSAGE, "ip_1", "ip_2", "ip_3", "ip_4");
@Override
public void getTags(String solrNode, Set<String> requestedTags, SnitchContext ctx) {