diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index ffd08eef660..5426eb8badd 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -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)
diff --git a/solr/core/src/java/org/apache/solr/cluster/SolrCollection.java b/solr/core/src/java/org/apache/solr/cluster/SolrCollection.java
index d22560ae659..ea7ea459de2 100644
--- a/solr/core/src/java/org/apache/solr/cluster/SolrCollection.java
+++ b/solr/core/src/java/org/apache/solr/cluster/SolrCollection.java
@@ -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 {
*
Using custom properties in conjunction with ad hoc {@link PlacementPlugin} code allows customizing placement
* decisions per collection.
*
- *
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
+ *
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);
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/AttributeFetcher.java b/solr/core/src/java/org/apache/solr/cluster/placement/AttributeFetcher.java
index 95783267a4f..ae2afc7b814 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/AttributeFetcher.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/AttributeFetcher.java
@@ -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;
*
Instances of this interface are used to fetch various attributes from nodes (and other sources) in the cluster.
*/
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> 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 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
- }
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/AttributeValues.java b/solr/core/src/java/org/apache/solr/cluster/placement/AttributeValues.java
index 24fcb6f0abd..b0e7e45c56e 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/AttributeValues.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/AttributeValues.java
@@ -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 getCoresCount(Node node);
-
- /**
- * For the given node: Hardware type of the disk partition where cores are stored
- */
- Optional getDiskType(Node node);
-
- /**
- * For the given node: Free disk size in Gigabytes of the partition on which cores are stored
- */
- Optional getFreeDisk(Node node);
-
- /**
- * For the given node: Total disk size in Gigabytes of the partition on which cores are stored
- */
- Optional getTotalDisk(Node node);
-
- /**
- * For the given node: Percentage between 0 and 100 of used heap over max heap
- */
- Optional getHeapUsage(Node node);
-
- /**
- * For the given node: matches {@link java.lang.management.OperatingSystemMXBean#getSystemLoadAverage()}
- */
- Optional 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 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 getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry);
-
+ Optional getNodeMetric(Node node, NodeMetric metric);
/**
- * Get a non node related metric of specific scope and name
+ * Get collection metrics.
*/
- Optional getMetric(String scope, String metricName);
+ Optional getCollectionMetrics(String collectionName);
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/CollectionMetrics.java b/solr/core/src/java/org/apache/solr/cluster/placement/CollectionMetrics.java
new file mode 100644
index 00000000000..3d205160ffb
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/CollectionMetrics.java
@@ -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 getShardMetrics(String shardName);
+ Iterator iterator();
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/Metric.java b/solr/core/src/java/org/apache/solr/cluster/placement/Metric.java
new file mode 100644
index 00000000000..b5ba496b46e
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/Metric.java
@@ -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 {
+
+ /**
+ * 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);
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/NodeMetric.java b/solr/core/src/java/org/apache/solr/cluster/placement/NodeMetric.java
new file mode 100644
index 00000000000..14436bad3a5
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/NodeMetric.java
@@ -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 extends Metric {
+
+ /**
+ * 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
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetric.java b/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetric.java
new file mode 100644
index 00000000000..9e70d5385fd
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetric.java
@@ -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 solr.core.[collection].[replica] registry)
+ */
+public interface ReplicaMetric extends Metric {
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetrics.java b/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetrics.java
new file mode 100644
index 00000000000..4c061c8f3b9
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/ReplicaMetrics.java
@@ -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();
+ Optional getReplicaMetric(ReplicaMetric metric);
+ Iterator, Object>> iterator();
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/ShardMetrics.java b/solr/core/src/java/org/apache/solr/cluster/placement/ShardMetrics.java
new file mode 100644
index 00000000000..9fe03606324
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/ShardMetrics.java
@@ -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 getLeaderMetrics();
+ Optional getReplicaMetrics(String replicaName);
+ Iterator iterator();
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
index 3c3bf49d916..e3a7a3f9721 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
@@ -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 requestedNodeSystemPropertiesSnitchTags = new HashSet<>();
- Set requestedNodeMetricSnitchTags = new HashSet<>();
+ Set requestedNodeSystemSnitchTags = new HashSet<>();
+ Set> requestedNodeMetricSnitchTags = new HashSet<>();
+ Map>> requestedCollectionMetrics = new HashMap<>();
Set 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> 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 nodeToCoreCount = Maps.newHashMap();
- Map nodeToDiskType = Maps.newHashMap();
- Map nodeToFreeDisk = Maps.newHashMap();
- Map nodeToTotalDisk = Maps.newHashMap();
- Map nodeToHeapUsage = Maps.newHashMap();
- Map nodeToSystemLoadAverage = Maps.newHashMap();
- Map> syspropSnitchToNodeToValue = Maps.newHashMap();
- Map> metricSnitchToNodeToValue = Maps.newHashMap();
+ Map> systemSnitchToNodeToValue = new HashMap<>();
+ Map, Map> metricSnitchToNodeToValue = new HashMap<>();
+ Map collectionMetricsBuilders = new HashMap<>();
+ Map> nodeToReplicaInternalTags = new HashMap<>();
+ Map>> 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> 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 sysPropMap = Maps.newHashMap();
- syspropSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
+ Map> allSnitchTagsToInsertion = new HashMap<>();
+ for (String sysPropSnitch : requestedNodeSystemSnitchTags) {
+ final Map sysPropMap = new HashMap<>();
+ systemSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
allSnitchTagsToInsertion.put(sysPropSnitch, (node, value) -> sysPropMap.put(node, (String) value));
}
- for (String metricSnitch : requestedNodeMetricSnitchTags) {
- final Map metricMap = Maps.newHashMap();
- metricSnitchToNodeToValue.put(metricSnitch, metricMap);
- allSnitchTagsToInsertion.put(metricSnitch, (node, value) -> metricMap.put(node, (Double) value));
+ for (NodeMetric> metric : requestedNodeMetricSnitchTags) {
+ final Map 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 collectionTags = tags.stream().map(tag -> tag.getInternalName()).collect(Collectors.toSet());
+ collection.shards().forEach(shard ->
+ shard.replicas().forEach(replica -> {
+ Set 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 tagValues = cloudManager.getNodeStateProvider().getNodeValues(node.getName(), allSnitchTagsToInsertion.keySet());
for (Map.Entry 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 tags = nodeToReplicaInternalTags.get(node);
+ Map>> 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> requestedMetrics = requestedCollectionNamesMetrics.get(replica.getCollection());
+ requestedMetrics.forEach(metric -> {
+ replicaMetricsBuilder.addMetric(metric, replica.get(metric.getInternalName()));
+ });
+ });
+ });
+ });
+ }
+
+
+ Map 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;
+ }
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeValuesImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeValuesImpl.java
index ce68094efd6..873bd3d867c 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeValuesImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeValuesImpl.java
@@ -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 nodeToCoreCount;
- final Map nodeToDiskType;
- final Map nodeToFreeDisk;
- final Map nodeToTotalDisk;
- final Map nodeToHeapUsage;
- final Map nodeToSystemLoadAverage;
- final Map> syspropSnitchToNodeToValue;
- final Map> metricSnitchToNodeToValue;
+ // sysprop (or sysenv) name / node -> value
+ final Map> systemSnitchToNodeToValue;
+ // metricName / node -> value
+ final Map, Map> metricSnitchToNodeToValue;
+ // collection / shard / replica / metricName -> value
+ final Map collectionMetrics;
- public AttributeValuesImpl(Map nodeToCoreCount,
- Map nodeToDiskType,
- Map nodeToFreeDisk,
- Map nodeToTotalDisk,
- Map nodeToHeapUsage,
- Map nodeToSystemLoadAverage,
- Map> syspropSnitchToNodeToValue,
- Map> 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> systemSnitchToNodeToValue,
+ Map, Map> metricSnitchToNodeToValue,
+ Map collectionMetrics) {
+ this.systemSnitchToNodeToValue = systemSnitchToNodeToValue;
this.metricSnitchToNodeToValue = metricSnitchToNodeToValue;
- }
-
- @Override
- public Optional getCoresCount(Node node) {
- return Optional.ofNullable(nodeToCoreCount.get(node));
- }
-
- @Override
- public Optional getDiskType(Node node) {
- return Optional.ofNullable(nodeToDiskType.get(node));
- }
-
- @Override
- public Optional getFreeDisk(Node node) {
- return Optional.ofNullable(nodeToFreeDisk.get(node));
- }
-
- @Override
- public Optional getTotalDisk(Node node) {
- return Optional.ofNullable(nodeToTotalDisk.get(node));
- }
-
- @Override
- public Optional getHeapUsage(Node node) {
- return Optional.ofNullable(nodeToHeapUsage.get(node));
- }
-
- @Override
- public Optional getSystemLoadAverage(Node node) {
- return Optional.ofNullable(nodeToSystemLoadAverage.get(node));
+ this.collectionMetrics = collectionMetrics;
}
@Override
public Optional getSystemProperty(Node node, String name) {
- Map nodeToValue = syspropSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
+ Map nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
if (nodeToValue == null) {
return Optional.empty();
}
@@ -93,13 +55,7 @@ public class AttributeValuesImpl implements AttributeValues {
@Override
public Optional getEnvironmentVariable(Node node, String name) {
- // TODO implement
- return Optional.empty();
- }
-
- @Override
- public Optional getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry) {
- Map nodeToValue = metricSnitchToNodeToValue.get(AttributeFetcherImpl.getMetricSnitchTag(metricName, registry));
+ Map nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemEnvSnitchTag(name));
if (nodeToValue == null) {
return Optional.empty();
}
@@ -107,8 +63,17 @@ public class AttributeValuesImpl implements AttributeValues {
}
@Override
- public Optional getMetric(String scope, String metricName) {
- // TODO implement
- return Optional.empty();
+ @SuppressWarnings("unchecked")
+ public Optional getNodeMetric(Node node, NodeMetric metric) {
+ Map nodeToValue = metricSnitchToNodeToValue.get(metric);
+ if (nodeToValue == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable((T) nodeToValue.get(node));
+ }
+
+ @Override
+ public Optional getCollectionMetrics(String collectionName) {
+ return Optional.ofNullable(collectionMetrics.get(collectionName));
}
}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java
new file mode 100644
index 00000000000..f8ea0cba97c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/CollectionMetricsBuilder.java
@@ -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 shardMetricsBuilders = new HashMap<>();
+
+
+ public Map getShardMetricsBuilders() {
+ return shardMetricsBuilders;
+ }
+
+ public CollectionMetrics build() {
+ final Map metricsMap = new HashMap<>();
+ shardMetricsBuilders.forEach((shard, builder) -> metricsMap.put(shard, builder.build()));
+ return new CollectionMetrics() {
+ @Override
+ public Optional getShardMetrics(String shardName) {
+ return Optional.ofNullable(metricsMap.get(shardName));
+ }
+
+ @Override
+ public Iterator iterator() {
+ return metricsMap.values().iterator();
+ }
+ };
+ }
+
+ public static class ShardMetricsBuilder {
+ final Map replicaMetricsBuilders = new HashMap<>();
+ final String shardName;
+ ReplicaMetricsBuilder leaderMetricsBuilder;
+
+ public ShardMetricsBuilder(String shardName) {
+ this.shardName = shardName;
+ }
+
+ public Map 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 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 getLeaderMetrics() {
+ return Optional.ofNullable(finalLeaderMetrics);
+ }
+
+ @Override
+ public Optional getReplicaMetrics(String replicaName) {
+ return Optional.ofNullable(metricsMap.get(replicaName));
+ }
+
+ @Override
+ public Iterator iterator() {
+ return metricsMap.values().iterator();
+ }
+ };
+ }
+ }
+
+ public static class ReplicaMetricsBuilder {
+ final Map, 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 Optional getReplicaMetric(ReplicaMetric metric) {
+ return Optional.ofNullable((T) metrics.get(metric));
+ }
+
+ @Override
+ public Iterator, Object>> iterator() {
+ return metrics.entrySet().iterator();
+ }
+ };
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/MetricImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/MetricImpl.java
new file mode 100644
index 00000000000..7b789d7a087
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/MetricImpl.java
@@ -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 implements Metric {
+
+ 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