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 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 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 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 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 + + "}"; + } +} diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java new file mode 100644 index 00000000000..8c273316d17 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java @@ -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 extends MetricImpl implements NodeMetric { + + /** Total disk space in GB. */ + public static final NodeMetricImpl 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 FREE_DISK_GB = new NodeMetricImpl<>("freeDisk", + Registry.SOLR_NODE, "CONTAINER.fs.usableSpace", BYTES_TO_GB_CONVERTER); + + /** Number of all cores. */ + public static final NodeMetricImpl NUM_CORES = new NodeMetricImpl<>(ImplicitSnitch.CORES); + public static final NodeMetricImpl HEAP_USAGE = new NodeMetricImpl<>(ImplicitSnitch.HEAPUSAGE); + + /** System load average. */ + public static final NodeMetricImpl SYSLOAD_AVG = + new NodeMetricImpl<>("sysLoadAvg", Registry.SOLR_JVM, "os.systemLoadAverage"); + + /** Number of available processors. */ + public static final NodeMetricImpl 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 converter) { + super(name, internalName, converter); + Objects.requireNonNull(registry); + this.registry = registry; + } + + public NodeMetricImpl(String key) { + this(key, null); + } + + public NodeMetricImpl(String key, Function 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 + "}"; + } + } +} diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/ReplicaMetricImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/ReplicaMetricImpl.java new file mode 100644 index 00000000000..f793a64fe22 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/ReplicaMetricImpl.java @@ -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 solr.core.[collection].[replica] registry) + */ +public class ReplicaMetricImpl extends MetricImpl implements ReplicaMetric { + + public static final ReplicaMetricImpl INDEX_SIZE_GB = new ReplicaMetricImpl<>("sizeGB", "INDEX.sizeInBytes", BYTES_TO_GB_CONVERTER); + + public static final ReplicaMetricImpl QUERY_RATE_1MIN = new ReplicaMetricImpl<>("queryRate", "QUERY./select.requestTimes:1minRate"); + public static final ReplicaMetricImpl 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 converter) { + super(name, internalName, converter); + } +} diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java index f5729c5f339..9c50289e554 100644 --- a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java +++ b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactory.java @@ -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 node with high free space since it needs to come later in sort order return Boolean.compare(aHasLowFreeSpace, bHasLowFreeSpace); diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/MinimizeCoresPlacementFactory.java b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/MinimizeCoresPlacementFactory.java index 5038ddd3543..bb1e762d12c 100644 --- a/solr/core/src/java/org/apache/solr/cluster/placement/plugins/MinimizeCoresPlacementFactory.java +++ b/solr/core/src/java/org/apache/solr/cluster/placement/plugins/MinimizeCoresPlacementFactory.java @@ -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 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 replicaPlacements = new HashSet<>(totalReplicasPerShard * request.getShardNames().size()); diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java index 0c983f19597..3a294c5985f 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java @@ -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); } diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/AttributeFetcherForTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/AttributeFetcherForTest.java index b1cc2a0dd79..bcc300ba108 100644 --- a/solr/core/src/test/org/apache/solr/cluster/placement/AttributeFetcherForTest.java +++ b/solr/core/src/test/org/apache/solr/cluster/placement/AttributeFetcherForTest.java @@ -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> 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; diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/Builders.java b/solr/core/src/test/org/apache/solr/cluster/placement/Builders.java index 398b16d1709..21b8369fecb 100644 --- a/solr/core/src/test/org/apache/solr/cluster/placement/Builders.java +++ b/solr/core/src/test/org/apache/solr/cluster/placement/Builders.java @@ -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 nodeToCoreCount = new HashMap<>(); - Map nodeToFreeDisk = new HashMap<>(); Map> sysprops = new HashMap<>(); - Map> metrics = new HashMap<>(); + Map, Map> metrics = new HashMap<>(); + Map 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 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 shardBuilders = new LinkedList<>(); private Map 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 nodes) { + int countPullReplicas, List 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 nodes, + List initialSizeGBPerShard) { Iterator 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 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> 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, 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 sysprops = null; - private Map metrics = null; + private Map, 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 getSysprops() { return sysprops; } - public Map getMetrics() { + public Map, Object> getMetrics() { return metrics; } diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/BuildersTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/BuildersTest.java new file mode 100644 index 00000000000..5b12b43f0f1 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/cluster/placement/BuildersTest.java @@ -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 coreCount = attributeValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES); + assertTrue("coreCount present", coreCount.isPresent()); + Optional 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 collectionMetricsOpt = attributeValues.getCollectionMetrics(collectionName); + assertTrue("collectionMetrics present", collectionMetricsOpt.isPresent()); + CollectionMetrics collectionMetrics = collectionMetricsOpt.get(); + for (String shardName : collection.getShardNames()) { + Optional shardMetricsOpt = collectionMetrics.getShardMetrics(shardName); + assertTrue("shard metrics", shardMetricsOpt.isPresent()); + ShardMetrics shardMetrics = shardMetricsOpt.get(); + Optional replicaMetricsOpt = shardMetrics.getLeaderMetrics(); + assertTrue("leader metrics", replicaMetricsOpt.isPresent()); + ReplicaMetrics leaderMetrics = replicaMetricsOpt.get(); + Optional 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 metricsOpt = shardMetrics.getReplicaMetrics(r.getReplicaName()); + assertTrue("replica metrics", metricsOpt.isPresent()); + ReplicaMetrics metrics = metricsOpt.get(); + Optional 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()); + } + }); + } + } +} diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java index 676c70fe14b..696755074b2 100644 --- a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java +++ b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java @@ -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 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 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 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 userNameOpt = attributeValues.getNodeMetric(node, someMetricKey); + assertTrue("user.name", userNameOpt.isPresent()); + assertEquals("userName", userName, userNameOpt.get()); + Optional syspropOpt = attributeValues.getSystemProperty(node, sysprop); + assertTrue("sysprop", syspropOpt.isPresent()); + assertEquals("user.name sysprop", userName, syspropOpt.get()); + Optional 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 shardMetricsOpt = collectionMetrics.getShardMetrics(shard.getShardName()); + assertTrue("shard metrics", shardMetricsOpt.isPresent()); + shard.replicas().forEach(replica -> { + Optional replicaMetricsOpt = shardMetricsOpt.get().getReplicaMetrics(replica.getReplicaName()); + assertTrue("replica metrics", replicaMetricsOpt.isPresent()); + ReplicaMetrics replicaMetrics = replicaMetricsOpt.get(); + Optional 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); diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java index e0486172853..81dda9d1bce 100644 --- a/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java +++ b/solr/core/src/test/org/apache/solr/cluster/placement/plugins/AffinityPlacementFactoryTest.java @@ -79,8 +79,8 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 { Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(2); LinkedList 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 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 liveNodes = clusterBuilder.buildLiveNodes(); @@ -190,7 +190,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 { LinkedList 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 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 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); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java index cba6a079282..ccc6583d28d 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java @@ -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>>> 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 fetchReplicaMetrics(String node, Map> metricsKeyVsTagReplica) { - Map collect = metricsKeyVsTagReplica.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getKey)); + Map> 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 metricsKeyVsTag) { + static void fetchReplicaMetrics(String solrNode, ClientSnitchCtx ctx, Map> 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 p = (Pair) ((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 p = (Pair) ((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 requestedTags, SnitchContext ctx) { if (!((ClientSnitchCtx)ctx).isNodeAlive(solrNode)) return; ClientSnitchCtx snitchContext = (ClientSnitchCtx) ctx; - Map metricsKeyVsTag = new HashMap<>(); + Map> 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>) 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"); diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java b/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java index 4aa7f4f16e7..acc412f22bd 100644 --- a/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java +++ b/solr/solrj/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java @@ -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 IP_SNITCHES = Collections.unmodifiableList(Arrays.asList("ip_1", "ip_2", "ip_3", "ip_4")); - public static final Set tags = Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, "ip_1", "ip_2", "ip_3", "ip_4"); + public static final Set 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 requestedTags, SnitchContext ctx) {