mirror of https://github.com/apache/lucene.git
SOLR-15019: Replica placement API needs a way to fetch existing replica metrics.
This commit is contained in:
parent
dc99e39d21
commit
2695624a9f
|
@ -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)
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.apache.solr.cluster;
|
||||
|
||||
import org.apache.solr.cluster.placement.AttributeFetcher;
|
||||
import org.apache.solr.cluster.placement.AttributeValues;
|
||||
import org.apache.solr.cluster.placement.PlacementPlugin;
|
||||
import org.apache.solr.cluster.placement.PlacementRequest;
|
||||
|
||||
|
@ -69,12 +68,11 @@ public interface SolrCollection {
|
|||
* <p>Using custom properties in conjunction with ad hoc {@link PlacementPlugin} code allows customizing placement
|
||||
* decisions per collection.
|
||||
*
|
||||
* <p>For example if a collection is to be placed only on nodes using SSD storage and not rotating disks, it can be
|
||||
* identified as such using some custom property (collection property could for example be called "driveType" and have
|
||||
* value "ssd" in that case), and the placement plugin (implementing {@link PlacementPlugin}) would then
|
||||
* <p>For example if a collection is to be placed only on nodes using located in a specific availability zone, it can be
|
||||
* identified as such using some custom property (collection property could for example be called "availabilityZone" and have
|
||||
* value "az1" in that case), and the placement plugin (implementing {@link PlacementPlugin}) would then
|
||||
* {@link AttributeFetcher#requestNodeSystemProperty(String)} for that property from all nodes and only place replicas
|
||||
* of this collection on {@link Node}'s for which
|
||||
* {@link AttributeValues#getDiskType(Node)} is non empty and equal to {@link org.apache.solr.cluster.placement.AttributeFetcher.DiskHardwareType#SSD}.
|
||||
* of this collection on {@link Node}'s for which this attribute is non empty and equal.
|
||||
*/
|
||||
String getCustomProperty(String customPropertyName);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -25,86 +26,45 @@ import java.util.Set;
|
|||
* <p>Instances of this interface are used to fetch various attributes from nodes (and other sources) in the cluster.</p>
|
||||
*/
|
||||
public interface AttributeFetcher {
|
||||
/**
|
||||
* Request the number of cores on each node. To get the value use {@link AttributeValues#getCoresCount(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeCoreCount();
|
||||
|
||||
/**
|
||||
* Request the disk hardware type on each node. To get the value use {@link AttributeValues#getDiskType(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeDiskType();
|
||||
|
||||
/**
|
||||
* Request the free disk size on each node. To get the value use {@link AttributeValues#getFreeDisk(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeFreeDisk();
|
||||
|
||||
/**
|
||||
* Request the total disk size on each node. To get the value use {@link AttributeValues#getTotalDisk(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeTotalDisk();
|
||||
|
||||
/**
|
||||
* Request the heap usage on each node. To get the value use {@link AttributeValues#getHeapUsage(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeHeapUsage();
|
||||
|
||||
/**
|
||||
* Request the system load average on each node. To get the value use {@link AttributeValues#getSystemLoadAverage(Node)}
|
||||
*/
|
||||
AttributeFetcher requestNodeSystemLoadAverage();
|
||||
|
||||
/**
|
||||
* Request a given system property on each node. To get the value use {@link AttributeValues#getSystemProperty(Node, String)}
|
||||
* @param name system property name
|
||||
*/
|
||||
AttributeFetcher requestNodeSystemProperty(String name);
|
||||
|
||||
/**
|
||||
* Request an environment variable on each node. To get the value use {@link AttributeValues#getEnvironmentVariable(Node, String)}
|
||||
* @param name environment property name
|
||||
*/
|
||||
AttributeFetcher requestNodeEnvironmentVariable(String name);
|
||||
|
||||
/**
|
||||
* Request a node metric from each node. To get the value use {@link AttributeValues#getMetric(Node, String, NodeMetricRegistry)}
|
||||
* Request a node metric from each node. To get the value use {@link AttributeValues#getNodeMetric(Node, NodeMetric)}
|
||||
* @param metric metric to retrieve (see {@link NodeMetric})
|
||||
*/
|
||||
AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry);
|
||||
AttributeFetcher requestNodeMetric(NodeMetric<?> metric);
|
||||
|
||||
/**
|
||||
* Request collection-level metrics. To get the values use {@link AttributeValues#getCollectionMetrics(String)}.
|
||||
* Note that this request will fetch information from nodes that are relevant to the collection
|
||||
* replicas and not the ones specified in {@link #fetchFrom(Set)} (though they may overlap).
|
||||
* @param solrCollection request metrics for this collection
|
||||
* @param metrics metrics to retrieve (see {@link ReplicaMetric})
|
||||
*/
|
||||
AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metrics);
|
||||
|
||||
/**
|
||||
* The set of nodes from which to fetch all node related attributes. Calling this method is mandatory if any of the {@code requestNode*}
|
||||
* methods got called.
|
||||
* @param nodes nodes to fetch from
|
||||
*/
|
||||
AttributeFetcher fetchFrom(Set<Node> nodes);
|
||||
|
||||
/**
|
||||
* Requests a (non node) metric of a given scope and name. To get the value use {@link AttributeValues#getMetric(String, String)}
|
||||
*/
|
||||
AttributeFetcher requestMetric(String scope, String metricName);
|
||||
|
||||
/**
|
||||
* Fetches all requested node attributes from all nodes passed to {@link #fetchFrom(Set)} as well as non node attributes
|
||||
* (those requested for example using {@link #requestMetric(String, String)}.
|
||||
* Fetches all requested node attributes from all nodes passed to {@link #fetchFrom(Set)} as well as non-node attributes
|
||||
* (those requested using e.g. {@link #requestCollectionMetrics(SolrCollection, Set)}.
|
||||
*
|
||||
* @return An instance allowing retrieval of all attributed that could be fetched.
|
||||
* @return An instance allowing retrieval of all attributes that could be fetched.
|
||||
*/
|
||||
AttributeValues fetchAttributes();
|
||||
|
||||
/**
|
||||
* Registry options for {@link Node} metrics.
|
||||
*/
|
||||
enum NodeMetricRegistry {
|
||||
/**
|
||||
* corresponds to solr.node
|
||||
*/
|
||||
SOLR_NODE,
|
||||
/**
|
||||
* corresponds to solr.jvm
|
||||
*/
|
||||
SOLR_JVM
|
||||
}
|
||||
|
||||
enum DiskHardwareType {
|
||||
SSD, ROTATIONAL
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,36 +22,6 @@ import org.apache.solr.cluster.Node;
|
|||
import java.util.Optional;
|
||||
|
||||
public interface AttributeValues {
|
||||
/**
|
||||
* For the given node: number of cores
|
||||
*/
|
||||
Optional<Integer> getCoresCount(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: Hardware type of the disk partition where cores are stored
|
||||
*/
|
||||
Optional<AttributeFetcher.DiskHardwareType> getDiskType(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: Free disk size in Gigabytes of the partition on which cores are stored
|
||||
*/
|
||||
Optional<Long> getFreeDisk(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: Total disk size in Gigabytes of the partition on which cores are stored
|
||||
*/
|
||||
Optional<Long> getTotalDisk(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: Percentage between 0 and 100 of used heap over max heap
|
||||
*/
|
||||
Optional<Double> getHeapUsage(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: matches {@link java.lang.management.OperatingSystemMXBean#getSystemLoadAverage()}
|
||||
*/
|
||||
Optional<Double> getSystemLoadAverage(Node node);
|
||||
|
||||
/**
|
||||
* For the given node: system property value (system properties are passed to Java using {@code -Dname=value}
|
||||
*/
|
||||
|
@ -63,13 +33,12 @@ public interface AttributeValues {
|
|||
Optional<String> getEnvironmentVariable(Node node, String name);
|
||||
|
||||
/**
|
||||
* For the given node: metric of specific name and registry
|
||||
* For the given node: metric identified by an instance of {@link NodeMetric}
|
||||
*/
|
||||
Optional<Double> getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry);
|
||||
|
||||
<T> Optional<T> getNodeMetric(Node node, NodeMetric<T> metric);
|
||||
|
||||
/**
|
||||
* Get a non node related metric of specific scope and name
|
||||
* Get collection metrics.
|
||||
*/
|
||||
Optional<Double> getMetric(String scope, String metricName);
|
||||
Optional<CollectionMetrics> getCollectionMetrics(String collectionName);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Collection-level metrics. Currently this class is a container just
|
||||
* for shard-level metrics but future versions may add other
|
||||
* primitive collection-level metrics.
|
||||
*/
|
||||
public interface CollectionMetrics {
|
||||
|
||||
Optional<ShardMetrics> getShardMetrics(String shardName);
|
||||
Iterator<ShardMetrics> iterator();
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
/**
|
||||
* Metric-related attribute of a node or replica. It defines a short symbolic name of the metric, the corresponding
|
||||
* internal metric name and the desired format/unit conversion. Generic type
|
||||
* specifies the type of converted values of this attribute.
|
||||
*/
|
||||
public interface Metric<T> {
|
||||
|
||||
/**
|
||||
* Return the short-hand name that identifies this attribute.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the internal name of a Solr metric associated with this attribute.
|
||||
*/
|
||||
String getInternalName();
|
||||
|
||||
/**
|
||||
* Convert raw value. This may involve changing raw value type or units.
|
||||
* @param value raw value
|
||||
* @return converted value
|
||||
*/
|
||||
T convert(Object value);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
/**
|
||||
* Node metric identifier, corresponding
|
||||
* to a node-level metric registry and the internal metric name.
|
||||
*/
|
||||
public interface NodeMetric<T> extends Metric<T> {
|
||||
|
||||
/**
|
||||
* Metric registry. If this metric identifier uses a fully-qualified
|
||||
* metric key instead, then this method will return {@link Registry#UNSPECIFIED}.
|
||||
*/
|
||||
Registry getRegistry();
|
||||
|
||||
/**
|
||||
* Registry options for node metrics.
|
||||
*/
|
||||
enum Registry {
|
||||
/**
|
||||
* corresponds to solr.node
|
||||
*/
|
||||
SOLR_NODE,
|
||||
/**
|
||||
* corresponds to solr.jvm
|
||||
*/
|
||||
SOLR_JVM,
|
||||
/**
|
||||
* corresponds to solr.jetty
|
||||
*/
|
||||
SOLR_JETTY,
|
||||
/**
|
||||
* In case when the registry name is not relevant (eg. a fully-qualified
|
||||
* metric key was provided as the metric name).
|
||||
*/
|
||||
UNSPECIFIED
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
/**
|
||||
* Replica metric identifier, corresponding to one of the
|
||||
* internal replica-level metric names (as reported in <code>solr.core.[collection].[replica]</code> registry)
|
||||
*/
|
||||
public interface ReplicaMetric<T> extends Metric<T> {
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Strongly-typed replica-level metrics.
|
||||
*/
|
||||
public interface ReplicaMetrics {
|
||||
|
||||
String getReplicaName();
|
||||
<T> Optional<T> getReplicaMetric(ReplicaMetric<T> metric);
|
||||
Iterator<Map.Entry<ReplicaMetric<?>, Object>> iterator();
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Shard-level metrics. Currently this is just a container for
|
||||
* replica-level metrics but future versions may add other
|
||||
* primitive shard-level metrics.
|
||||
*/
|
||||
public interface ShardMetrics {
|
||||
String getShardName();
|
||||
Optional<ReplicaMetrics> getLeaderMetrics();
|
||||
Optional<ReplicaMetrics> getReplicaMetrics(String replicaName);
|
||||
Iterator<ReplicaMetrics> iterator();
|
||||
}
|
|
@ -17,13 +17,17 @@
|
|||
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
|
||||
import org.apache.solr.client.solrj.impl.SolrClientNodeStateProvider;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
import org.apache.solr.cluster.placement.AttributeFetcher;
|
||||
import org.apache.solr.cluster.placement.AttributeValues;
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.placement.CollectionMetrics;
|
||||
import org.apache.solr.cluster.placement.NodeMetric;
|
||||
import org.apache.solr.cluster.placement.ReplicaMetric;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.cloud.Replica;
|
||||
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
|
||||
import org.apache.solr.core.SolrInfoBean;
|
||||
import org.apache.solr.metrics.SolrMetricManager;
|
||||
|
@ -33,18 +37,18 @@ import org.slf4j.LoggerFactory;
|
|||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AttributeFetcher} that uses {@link SolrCloudManager}
|
||||
* to access Solr cluster details.
|
||||
*/
|
||||
public class AttributeFetcherImpl implements AttributeFetcher {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
boolean requestedNodeCoreCount;
|
||||
boolean requestedNodeDiskType;
|
||||
boolean requestedNodeFreeDisk;
|
||||
boolean requestedNodeTotalDisk;
|
||||
boolean requestedNodeHeapUsage;
|
||||
boolean requestedNodeSystemLoadAverage;
|
||||
Set<String> requestedNodeSystemPropertiesSnitchTags = new HashSet<>();
|
||||
Set<String> requestedNodeMetricSnitchTags = new HashSet<>();
|
||||
Set<String> requestedNodeSystemSnitchTags = new HashSet<>();
|
||||
Set<NodeMetric<?>> requestedNodeMetricSnitchTags = new HashSet<>();
|
||||
Map<SolrCollection, Set<ReplicaMetric<?>>> requestedCollectionMetrics = new HashMap<>();
|
||||
|
||||
Set<Node> nodes = Collections.emptySet();
|
||||
|
||||
|
@ -54,56 +58,29 @@ public class AttributeFetcherImpl implements AttributeFetcher {
|
|||
this.cloudManager = cloudManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeCoreCount() {
|
||||
requestedNodeCoreCount = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeDiskType() {
|
||||
requestedNodeDiskType = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeFreeDisk() {
|
||||
requestedNodeFreeDisk = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeTotalDisk() {
|
||||
requestedNodeTotalDisk = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeHeapUsage() {
|
||||
requestedNodeHeapUsage = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeSystemLoadAverage() {
|
||||
requestedNodeSystemLoadAverage = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeSystemProperty(String name) {
|
||||
requestedNodeSystemPropertiesSnitchTags.add(getSystemPropertySnitchTag(name));
|
||||
requestedNodeSystemSnitchTags.add(getSystemPropertySnitchTag(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeEnvironmentVariable(String name) {
|
||||
throw new UnsupportedOperationException("Not yet implemented...");
|
||||
requestedNodeSystemSnitchTags.add(getSystemEnvSnitchTag(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry) {
|
||||
requestedNodeMetricSnitchTags.add(getMetricSnitchTag(metricName, registry));
|
||||
public AttributeFetcher requestNodeMetric(NodeMetric<?> metric) {
|
||||
requestedNodeMetricSnitchTags.add(metric);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metrics) {
|
||||
if (!metrics.isEmpty()) {
|
||||
requestedCollectionMetrics.put(solrCollection, Set.copyOf(metrics));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -113,74 +90,49 @@ public class AttributeFetcherImpl implements AttributeFetcher {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestMetric(String scope, String metricName) {
|
||||
throw new UnsupportedOperationException("Not yet implemented...");
|
||||
}
|
||||
private static final double GB = 1024 * 1024 * 1024;
|
||||
|
||||
@Override
|
||||
public AttributeValues fetchAttributes() {
|
||||
// TODO Code here only supports node related attributes for now
|
||||
|
||||
// Maps in which attribute values will be added
|
||||
Map<Node, Integer> nodeToCoreCount = Maps.newHashMap();
|
||||
Map<Node, DiskHardwareType> nodeToDiskType = Maps.newHashMap();
|
||||
Map<Node, Long> nodeToFreeDisk = Maps.newHashMap();
|
||||
Map<Node, Long> nodeToTotalDisk = Maps.newHashMap();
|
||||
Map<Node, Double> nodeToHeapUsage = Maps.newHashMap();
|
||||
Map<Node, Double> nodeToSystemLoadAverage = Maps.newHashMap();
|
||||
Map<String, Map<Node, String>> syspropSnitchToNodeToValue = Maps.newHashMap();
|
||||
Map<String, Map<Node, Double>> metricSnitchToNodeToValue = Maps.newHashMap();
|
||||
Map<String, Map<Node, String>> systemSnitchToNodeToValue = new HashMap<>();
|
||||
Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue = new HashMap<>();
|
||||
Map<String, CollectionMetricsBuilder> collectionMetricsBuilders = new HashMap<>();
|
||||
Map<Node, Set<String>> nodeToReplicaInternalTags = new HashMap<>();
|
||||
Map<String, Set<ReplicaMetric<?>>> requestedCollectionNamesMetrics = requestedCollectionMetrics.entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey().getName(), e -> e.getValue()));
|
||||
|
||||
// In order to match the returned values for the various snitches, we need to keep track of where each
|
||||
// received value goes. Given the target maps are of different types (the maps from Node to whatever defined
|
||||
// above) we instead pass a function taking two arguments, the node and the (non null) returned value,
|
||||
// that will cast the value into the appropriate type for the snitch tag and insert it into the appropriate map
|
||||
// with the node as the key.
|
||||
Map<String, BiConsumer<Node, Object>> allSnitchTagsToInsertion = Maps.newHashMap();
|
||||
if (requestedNodeCoreCount) {
|
||||
allSnitchTagsToInsertion.put(ImplicitSnitch.CORES, (node, value) -> nodeToCoreCount.put(node, ((Number) value).intValue()));
|
||||
}
|
||||
if (requestedNodeDiskType) {
|
||||
allSnitchTagsToInsertion.put(ImplicitSnitch.DISKTYPE, (node, value) -> {
|
||||
if ("rotational".equals(value)) {
|
||||
nodeToDiskType.put(node, DiskHardwareType.ROTATIONAL);
|
||||
} else if ("ssd".equals(value)) {
|
||||
nodeToDiskType.put(node, DiskHardwareType.SSD);
|
||||
}
|
||||
// unknown disk type: insert no value, returned optional will be empty
|
||||
});
|
||||
}
|
||||
if (requestedNodeFreeDisk) {
|
||||
allSnitchTagsToInsertion.put(SolrClientNodeStateProvider.Variable.FREEDISK.tagName,
|
||||
// Convert from bytes to GB
|
||||
(node, value) -> nodeToFreeDisk.put(node, ((Number) value).longValue() / 1024 / 1024 / 1024));
|
||||
}
|
||||
if (requestedNodeTotalDisk) {
|
||||
allSnitchTagsToInsertion.put(SolrClientNodeStateProvider.Variable.TOTALDISK.tagName,
|
||||
// Convert from bytes to GB
|
||||
(node, value) -> nodeToTotalDisk.put(node, ((Number) value).longValue() / 1024 / 1024 / 1024));
|
||||
}
|
||||
if (requestedNodeHeapUsage) {
|
||||
allSnitchTagsToInsertion.put(ImplicitSnitch.HEAPUSAGE,
|
||||
(node, value) -> nodeToHeapUsage.put(node, ((Number) value).doubleValue()));
|
||||
}
|
||||
if (requestedNodeSystemLoadAverage) {
|
||||
allSnitchTagsToInsertion.put(ImplicitSnitch.SYSLOADAVG,
|
||||
(node, value) -> nodeToSystemLoadAverage.put(node, ((Number) value).doubleValue()));
|
||||
}
|
||||
for (String sysPropSnitch : requestedNodeSystemPropertiesSnitchTags) {
|
||||
final Map<Node, String> sysPropMap = Maps.newHashMap();
|
||||
syspropSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
|
||||
Map<String, BiConsumer<Node, Object>> allSnitchTagsToInsertion = new HashMap<>();
|
||||
for (String sysPropSnitch : requestedNodeSystemSnitchTags) {
|
||||
final Map<Node, String> sysPropMap = new HashMap<>();
|
||||
systemSnitchToNodeToValue.put(sysPropSnitch, sysPropMap);
|
||||
allSnitchTagsToInsertion.put(sysPropSnitch, (node, value) -> sysPropMap.put(node, (String) value));
|
||||
}
|
||||
for (String metricSnitch : requestedNodeMetricSnitchTags) {
|
||||
final Map<Node, Double> metricMap = Maps.newHashMap();
|
||||
metricSnitchToNodeToValue.put(metricSnitch, metricMap);
|
||||
allSnitchTagsToInsertion.put(metricSnitch, (node, value) -> metricMap.put(node, (Double) value));
|
||||
for (NodeMetric<?> metric : requestedNodeMetricSnitchTags) {
|
||||
final Map<Node, Object> metricMap = new HashMap<>();
|
||||
metricSnitchToNodeToValue.put(metric, metricMap);
|
||||
String metricSnitch = getMetricSnitchTag(metric);
|
||||
allSnitchTagsToInsertion.put(metricSnitch, (node, value) -> metricMap.put(node, metric.convert(value)));
|
||||
}
|
||||
requestedCollectionMetrics.forEach((collection, tags) -> {
|
||||
Set<String> collectionTags = tags.stream().map(tag -> tag.getInternalName()).collect(Collectors.toSet());
|
||||
collection.shards().forEach(shard ->
|
||||
shard.replicas().forEach(replica -> {
|
||||
Set<String> perNodeInternalTags = nodeToReplicaInternalTags
|
||||
.computeIfAbsent(replica.getNode(), n -> new HashSet<>());
|
||||
perNodeInternalTags.addAll(collectionTags);
|
||||
}));
|
||||
});
|
||||
|
||||
// Now that we know everything we need to fetch (and where to put it), just do it.
|
||||
// TODO: we could probably fetch this in parallel - for large clusters this could
|
||||
// significantly shorten the execution time
|
||||
for (Node node : nodes) {
|
||||
Map<String, Object> tagValues = cloudManager.getNodeStateProvider().getNodeValues(node.getName(), allSnitchTagsToInsertion.keySet());
|
||||
for (Map.Entry<String, Object> e : tagValues.entrySet()) {
|
||||
|
@ -197,32 +149,75 @@ public class AttributeFetcherImpl implements AttributeFetcher {
|
|||
}
|
||||
}
|
||||
|
||||
return new AttributeValuesImpl(nodeToCoreCount,
|
||||
nodeToDiskType,
|
||||
nodeToFreeDisk,
|
||||
nodeToTotalDisk,
|
||||
nodeToHeapUsage,
|
||||
nodeToSystemLoadAverage,
|
||||
syspropSnitchToNodeToValue,
|
||||
metricSnitchToNodeToValue);
|
||||
for (Node node : nodeToReplicaInternalTags.keySet()) {
|
||||
Set<String> tags = nodeToReplicaInternalTags.get(node);
|
||||
Map<String, Map<String, List<Replica>>> infos = cloudManager.getNodeStateProvider().getReplicaInfo(node.getName(), tags);
|
||||
infos.entrySet().stream()
|
||||
.filter(entry -> requestedCollectionNamesMetrics.containsKey(entry.getKey()))
|
||||
.forEach(entry -> {
|
||||
CollectionMetricsBuilder collectionMetricsBuilder = collectionMetricsBuilders
|
||||
.computeIfAbsent(entry.getKey(), c -> new CollectionMetricsBuilder());
|
||||
entry.getValue().forEach((shardName, replicas) -> {
|
||||
CollectionMetricsBuilder.ShardMetricsBuilder shardMetricsBuilder =
|
||||
collectionMetricsBuilder.getShardMetricsBuilders()
|
||||
.computeIfAbsent(shardName, s -> new CollectionMetricsBuilder.ShardMetricsBuilder(s));
|
||||
replicas.forEach(replica -> {
|
||||
CollectionMetricsBuilder.ReplicaMetricsBuilder replicaMetricsBuilder =
|
||||
shardMetricsBuilder.getReplicaMetricsBuilders()
|
||||
.computeIfAbsent(replica.getName(), n -> new CollectionMetricsBuilder.ReplicaMetricsBuilder(n));
|
||||
replicaMetricsBuilder.setLeader(replica.isLeader());
|
||||
if (replica.isLeader()) {
|
||||
shardMetricsBuilder.setLeaderMetrics(replicaMetricsBuilder);
|
||||
}
|
||||
Set<ReplicaMetric<?>> requestedMetrics = requestedCollectionNamesMetrics.get(replica.getCollection());
|
||||
requestedMetrics.forEach(metric -> {
|
||||
replicaMetricsBuilder.addMetric(metric, replica.get(metric.getInternalName()));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Map<String, CollectionMetrics> collectionMetrics = new HashMap<>();
|
||||
collectionMetricsBuilders.forEach((name, builder) -> collectionMetrics.put(name, builder.build()));
|
||||
|
||||
return new AttributeValuesImpl(systemSnitchToNodeToValue,
|
||||
metricSnitchToNodeToValue, collectionMetrics);
|
||||
}
|
||||
|
||||
private static SolrInfoBean.Group getGroupFromMetricRegistry(NodeMetricRegistry registry) {
|
||||
private static SolrInfoBean.Group getGroupFromMetricRegistry(NodeMetric.Registry registry) {
|
||||
switch (registry) {
|
||||
case SOLR_JVM:
|
||||
return SolrInfoBean.Group.jvm;
|
||||
case SOLR_NODE:
|
||||
return SolrInfoBean.Group.node;
|
||||
case SOLR_JETTY:
|
||||
return SolrInfoBean.Group.jetty;
|
||||
default:
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unsupported registry value " + registry);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMetricSnitchTag(String metricName, NodeMetricRegistry registry) {
|
||||
return SolrClientNodeStateProvider.METRICS_PREFIX + SolrMetricManager.getRegistryName(getGroupFromMetricRegistry(registry), metricName);
|
||||
public static String getMetricSnitchTag(NodeMetric<?> metric) {
|
||||
if (metric.getRegistry() != NodeMetric.Registry.UNSPECIFIED) {
|
||||
// regular registry + metricName
|
||||
return SolrClientNodeStateProvider.METRICS_PREFIX +
|
||||
SolrMetricManager.getRegistryName(getGroupFromMetricRegistry(metric.getRegistry())) + ":" + metric.getInternalName();
|
||||
} else if (ImplicitSnitch.tags.contains(metric.getInternalName())) {
|
||||
// "special" well-known tag
|
||||
return metric.getInternalName();
|
||||
} else {
|
||||
// a fully-qualified metric key
|
||||
return SolrClientNodeStateProvider.METRICS_PREFIX + metric.getInternalName();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSystemPropertySnitchTag(String name) {
|
||||
return ImplicitSnitch.SYSPROP + name;
|
||||
}
|
||||
|
||||
public static String getSystemEnvSnitchTag(String name) {
|
||||
return ImplicitSnitch.SYSENV + name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,74 +17,36 @@
|
|||
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import org.apache.solr.cluster.placement.AttributeFetcher;
|
||||
import org.apache.solr.cluster.placement.AttributeValues;
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.placement.CollectionMetrics;
|
||||
import org.apache.solr.cluster.placement.NodeMetric;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AttributeValues} used by {@link AttributeFetcherImpl}.
|
||||
*/
|
||||
public class AttributeValuesImpl implements AttributeValues {
|
||||
final Map<Node, Integer> nodeToCoreCount;
|
||||
final Map<Node, AttributeFetcher.DiskHardwareType> nodeToDiskType;
|
||||
final Map<Node, Long> nodeToFreeDisk;
|
||||
final Map<Node, Long> nodeToTotalDisk;
|
||||
final Map<Node, Double> nodeToHeapUsage;
|
||||
final Map<Node, Double> nodeToSystemLoadAverage;
|
||||
final Map<String, Map<Node, String>> syspropSnitchToNodeToValue;
|
||||
final Map<String, Map<Node, Double>> metricSnitchToNodeToValue;
|
||||
// sysprop (or sysenv) name / node -> value
|
||||
final Map<String, Map<Node, String>> systemSnitchToNodeToValue;
|
||||
// metricName / node -> value
|
||||
final Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue;
|
||||
// collection / shard / replica / metricName -> value
|
||||
final Map<String, CollectionMetrics> collectionMetrics;
|
||||
|
||||
public AttributeValuesImpl(Map<Node, Integer> nodeToCoreCount,
|
||||
Map<Node, AttributeFetcher.DiskHardwareType> nodeToDiskType,
|
||||
Map<Node, Long> nodeToFreeDisk,
|
||||
Map<Node, Long> nodeToTotalDisk,
|
||||
Map<Node, Double> nodeToHeapUsage,
|
||||
Map<Node, Double> nodeToSystemLoadAverage,
|
||||
Map<String, Map<Node, String>> syspropSnitchToNodeToValue,
|
||||
Map<String, Map<Node, Double>> metricSnitchToNodeToValue) {
|
||||
this.nodeToCoreCount = nodeToCoreCount;
|
||||
this.nodeToDiskType = nodeToDiskType;
|
||||
this.nodeToFreeDisk = nodeToFreeDisk;
|
||||
this.nodeToTotalDisk = nodeToTotalDisk;
|
||||
this.nodeToHeapUsage = nodeToHeapUsage;
|
||||
this.nodeToSystemLoadAverage = nodeToSystemLoadAverage;
|
||||
this.syspropSnitchToNodeToValue = syspropSnitchToNodeToValue;
|
||||
public AttributeValuesImpl(Map<String, Map<Node, String>> systemSnitchToNodeToValue,
|
||||
Map<NodeMetric<?>, Map<Node, Object>> metricSnitchToNodeToValue,
|
||||
Map<String, CollectionMetrics> collectionMetrics) {
|
||||
this.systemSnitchToNodeToValue = systemSnitchToNodeToValue;
|
||||
this.metricSnitchToNodeToValue = metricSnitchToNodeToValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Integer> getCoresCount(Node node) {
|
||||
return Optional.ofNullable(nodeToCoreCount.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AttributeFetcher.DiskHardwareType> getDiskType(Node node) {
|
||||
return Optional.ofNullable(nodeToDiskType.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getFreeDisk(Node node) {
|
||||
return Optional.ofNullable(nodeToFreeDisk.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getTotalDisk(Node node) {
|
||||
return Optional.ofNullable(nodeToTotalDisk.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getHeapUsage(Node node) {
|
||||
return Optional.ofNullable(nodeToHeapUsage.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getSystemLoadAverage(Node node) {
|
||||
return Optional.ofNullable(nodeToSystemLoadAverage.get(node));
|
||||
this.collectionMetrics = collectionMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getSystemProperty(Node node, String name) {
|
||||
Map<Node, String> nodeToValue = syspropSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
|
||||
Map<Node, String> nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemPropertySnitchTag(name));
|
||||
if (nodeToValue == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -93,13 +55,7 @@ public class AttributeValuesImpl implements AttributeValues {
|
|||
|
||||
@Override
|
||||
public Optional<String> getEnvironmentVariable(Node node, String name) {
|
||||
// TODO implement
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getMetric(Node node, String metricName, AttributeFetcher.NodeMetricRegistry registry) {
|
||||
Map<Node, Double> nodeToValue = metricSnitchToNodeToValue.get(AttributeFetcherImpl.getMetricSnitchTag(metricName, registry));
|
||||
Map<Node, String> nodeToValue = systemSnitchToNodeToValue.get(AttributeFetcherImpl.getSystemEnvSnitchTag(name));
|
||||
if (nodeToValue == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -107,8 +63,17 @@ public class AttributeValuesImpl implements AttributeValues {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<Double> getMetric(String scope, String metricName) {
|
||||
// TODO implement
|
||||
return Optional.empty();
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getNodeMetric(Node node, NodeMetric<T> metric) {
|
||||
Map<Node, Object> nodeToValue = metricSnitchToNodeToValue.get(metric);
|
||||
if (nodeToValue == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable((T) nodeToValue.get(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CollectionMetrics> getCollectionMetrics(String collectionName) {
|
||||
return Optional.ofNullable(collectionMetrics.get(collectionName));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import org.apache.solr.cluster.placement.CollectionMetrics;
|
||||
import org.apache.solr.cluster.placement.ReplicaMetric;
|
||||
import org.apache.solr.cluster.placement.ReplicaMetrics;
|
||||
import org.apache.solr.cluster.placement.ShardMetrics;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Builder class for constructing instances of {@link CollectionMetrics}.
|
||||
*/
|
||||
public class CollectionMetricsBuilder {
|
||||
|
||||
final Map<String, ShardMetricsBuilder> shardMetricsBuilders = new HashMap<>();
|
||||
|
||||
|
||||
public Map<String, ShardMetricsBuilder> getShardMetricsBuilders() {
|
||||
return shardMetricsBuilders;
|
||||
}
|
||||
|
||||
public CollectionMetrics build() {
|
||||
final Map<String, ShardMetrics> metricsMap = new HashMap<>();
|
||||
shardMetricsBuilders.forEach((shard, builder) -> metricsMap.put(shard, builder.build()));
|
||||
return new CollectionMetrics() {
|
||||
@Override
|
||||
public Optional<ShardMetrics> getShardMetrics(String shardName) {
|
||||
return Optional.ofNullable(metricsMap.get(shardName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ShardMetrics> iterator() {
|
||||
return metricsMap.values().iterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class ShardMetricsBuilder {
|
||||
final Map<String, ReplicaMetricsBuilder> replicaMetricsBuilders = new HashMap<>();
|
||||
final String shardName;
|
||||
ReplicaMetricsBuilder leaderMetricsBuilder;
|
||||
|
||||
public ShardMetricsBuilder(String shardName) {
|
||||
this.shardName = shardName;
|
||||
}
|
||||
|
||||
public Map<String, ReplicaMetricsBuilder> getReplicaMetricsBuilders() {
|
||||
return replicaMetricsBuilders;
|
||||
}
|
||||
|
||||
public ShardMetricsBuilder setLeaderMetrics(ReplicaMetricsBuilder replicaMetricsBuilder) {
|
||||
leaderMetricsBuilder = replicaMetricsBuilder;
|
||||
if (leaderMetricsBuilder != null) {
|
||||
replicaMetricsBuilders.put(leaderMetricsBuilder.replicaName, leaderMetricsBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShardMetrics build() {
|
||||
final Map<String, ReplicaMetrics> metricsMap = new HashMap<>();
|
||||
replicaMetricsBuilders.forEach((name, replicaBuilder) -> {
|
||||
ReplicaMetrics metrics = replicaBuilder.build();
|
||||
metricsMap.put(name, metrics);
|
||||
if (replicaBuilder.leader) {
|
||||
if (leaderMetricsBuilder == null) {
|
||||
leaderMetricsBuilder = replicaBuilder;
|
||||
} else if (!leaderMetricsBuilder.replicaName.equals(replicaBuilder.replicaName)) {
|
||||
throw new RuntimeException("two replicas claim to be the shard leader! existing=" +
|
||||
leaderMetricsBuilder + " and current " + replicaBuilder);
|
||||
}
|
||||
}
|
||||
});
|
||||
final ReplicaMetrics finalLeaderMetrics = leaderMetricsBuilder != null ? leaderMetricsBuilder.build() : null;
|
||||
return new ShardMetrics() {
|
||||
@Override
|
||||
public String getShardName() {
|
||||
return shardName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReplicaMetrics> getLeaderMetrics() {
|
||||
return Optional.ofNullable(finalLeaderMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ReplicaMetrics> getReplicaMetrics(String replicaName) {
|
||||
return Optional.ofNullable(metricsMap.get(replicaName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ReplicaMetrics> iterator() {
|
||||
return metricsMap.values().iterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReplicaMetricsBuilder {
|
||||
final Map<ReplicaMetric<?>, Object> metrics = new HashMap<>();
|
||||
final String replicaName;
|
||||
boolean leader;
|
||||
|
||||
public ReplicaMetricsBuilder(String replicaName) {
|
||||
this.replicaName = replicaName;
|
||||
}
|
||||
|
||||
public ReplicaMetricsBuilder setLeader(boolean leader) {
|
||||
this.leader = leader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Add unconverted (raw) values here, this method internally calls
|
||||
* {@link ReplicaMetric#convert(Object)}.
|
||||
* @param metric metric to add
|
||||
* @param value raw (unconverted) metric value
|
||||
*/
|
||||
public ReplicaMetricsBuilder addMetric(ReplicaMetric<?> metric, Object value) {
|
||||
value = metric.convert(value);
|
||||
if (value != null) {
|
||||
metrics.put(metric, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReplicaMetrics build() {
|
||||
return new ReplicaMetrics() {
|
||||
@Override
|
||||
public String getReplicaName() {
|
||||
return replicaName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getReplicaMetric(ReplicaMetric<T> metric) {
|
||||
return Optional.ofNullable((T) metrics.get(metric));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<ReplicaMetric<?>, Object>> iterator() {
|
||||
return metrics.entrySet().iterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import org.apache.solr.cluster.placement.Metric;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Base class for {@link Metric} implementations.
|
||||
*/
|
||||
public abstract class MetricImpl<T> implements Metric<T> {
|
||||
|
||||
public static final double GB = 1024 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Identity converter. It returns the raw value unchanged IFF
|
||||
* the value's type can be cast to the generic type of this attribute,
|
||||
* otherwise it returns null.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final Function<Object, T> IDENTITY_CONVERTER = v -> {
|
||||
try {
|
||||
return (T) v;
|
||||
} catch (ClassCastException cce) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bytes to gigabytes converter. Supports converting number or string
|
||||
* representations of raw values expressed in bytes.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static final Function<Object, Double> BYTES_TO_GB_CONVERTER = v -> {
|
||||
double sizeInBytes;
|
||||
if (!(v instanceof Number)) {
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
sizeInBytes = Double.parseDouble(String.valueOf(v));
|
||||
} catch (Exception nfe) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
sizeInBytes = ((Number) v).doubleValue();
|
||||
}
|
||||
return sizeInBytes / GB;
|
||||
};
|
||||
|
||||
protected final String name;
|
||||
protected final String internalName;
|
||||
protected final Function<Object, T> converter;
|
||||
|
||||
/**
|
||||
* Create a metric attribute.
|
||||
* @param name short-hand name that identifies this attribute.
|
||||
* @param internalName internal name of a Solr metric.
|
||||
*/
|
||||
public MetricImpl(String name, String internalName) {
|
||||
this(name, internalName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a metric attribute.
|
||||
* @param name short-hand name that identifies this attribute.
|
||||
* @param internalName internal name of a Solr metric.
|
||||
* @param converter optional raw value converter. If null then
|
||||
* {@link #IDENTITY_CONVERTER} will be used.
|
||||
*/
|
||||
public MetricImpl(String name, String internalName, Function<Object, T> converter) {
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(internalName);
|
||||
this.name = name;
|
||||
this.internalName = internalName;
|
||||
if (converter == null) {
|
||||
this.converter = IDENTITY_CONVERTER;
|
||||
} else {
|
||||
this.converter = converter;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInternalName() {
|
||||
return internalName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(Object value) {
|
||||
return converter.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MetricImpl<?> that = (MetricImpl<?>) o;
|
||||
return name.equals(that.getName()) && internalName.equals(that.getInternalName()) && converter.equals(that.converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, internalName, converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"name=" + name +
|
||||
", internalName=" + internalName +
|
||||
"}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import org.apache.solr.cluster.placement.NodeMetric;
|
||||
import org.apache.solr.common.cloud.rule.ImplicitSnitch;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Node metric identifier, corresponding
|
||||
* to a node-level metric registry and the internal metric name.
|
||||
*/
|
||||
public class NodeMetricImpl<T> extends MetricImpl<T> implements NodeMetric<T> {
|
||||
|
||||
/** Total disk space in GB. */
|
||||
public static final NodeMetricImpl<Double> TOTAL_DISK_GB = new NodeMetricImpl<>("totalDisk",
|
||||
Registry.SOLR_NODE, "CONTAINER.fs.totalSpace", BYTES_TO_GB_CONVERTER);
|
||||
|
||||
/** Free (usable) disk space in GB. */
|
||||
public static final NodeMetricImpl<Double> FREE_DISK_GB = new NodeMetricImpl<>("freeDisk",
|
||||
Registry.SOLR_NODE, "CONTAINER.fs.usableSpace", BYTES_TO_GB_CONVERTER);
|
||||
|
||||
/** Number of all cores. */
|
||||
public static final NodeMetricImpl<Integer> NUM_CORES = new NodeMetricImpl<>(ImplicitSnitch.CORES);
|
||||
public static final NodeMetricImpl<Double> HEAP_USAGE = new NodeMetricImpl<>(ImplicitSnitch.HEAPUSAGE);
|
||||
|
||||
/** System load average. */
|
||||
public static final NodeMetricImpl<Double> SYSLOAD_AVG =
|
||||
new NodeMetricImpl<>("sysLoadAvg", Registry.SOLR_JVM, "os.systemLoadAverage");
|
||||
|
||||
/** Number of available processors. */
|
||||
public static final NodeMetricImpl<Integer> AVAILABLE_PROCESSORS =
|
||||
new NodeMetricImpl<>("availableProcessors", Registry.SOLR_JVM, "os.availableProcessors");
|
||||
|
||||
private final Registry registry;
|
||||
|
||||
public NodeMetricImpl(String name, Registry registry, String internalName) {
|
||||
this(name, registry, internalName, null);
|
||||
}
|
||||
|
||||
public NodeMetricImpl(String name, Registry registry, String internalName, Function<Object, T> converter) {
|
||||
super(name, internalName, converter);
|
||||
Objects.requireNonNull(registry);
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public NodeMetricImpl(String key) {
|
||||
this(key, null);
|
||||
}
|
||||
|
||||
public NodeMetricImpl(String key, Function<Object, T> converter) {
|
||||
super(key, key, converter);
|
||||
this.registry = Registry.UNSPECIFIED;
|
||||
}
|
||||
|
||||
public Registry getRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
NodeMetricImpl<?> that = (NodeMetricImpl<?>) o;
|
||||
return registry == that.registry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (registry != null) {
|
||||
return "NodeMetricImpl{" +
|
||||
"name='" + name + '\'' +
|
||||
", internalName='" + internalName + '\'' +
|
||||
", converter=" + converter +
|
||||
", registry=" + registry +
|
||||
'}';
|
||||
} else {
|
||||
return "NodeMetricImpl{key=" + internalName + "}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement.impl;
|
||||
|
||||
import org.apache.solr.cluster.placement.ReplicaMetric;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Replica metric identifier, corresponding to one of the
|
||||
* internal replica-level metric names (as reported in <code>solr.core.[collection].[replica]</code> registry)
|
||||
*/
|
||||
public class ReplicaMetricImpl<T> extends MetricImpl<T> implements ReplicaMetric<T> {
|
||||
|
||||
public static final ReplicaMetricImpl<Double> INDEX_SIZE_GB = new ReplicaMetricImpl<>("sizeGB", "INDEX.sizeInBytes", BYTES_TO_GB_CONVERTER);
|
||||
|
||||
public static final ReplicaMetricImpl<Double> QUERY_RATE_1MIN = new ReplicaMetricImpl<>("queryRate", "QUERY./select.requestTimes:1minRate");
|
||||
public static final ReplicaMetricImpl<Double> UPDATE_RATE_1MIN = new ReplicaMetricImpl<>("updateRate", "UPDATE./update.requestTimes:1minRate");
|
||||
|
||||
public ReplicaMetricImpl(String name, String internalName) {
|
||||
super(name, internalName);
|
||||
}
|
||||
|
||||
public ReplicaMetricImpl(String name, String internalName, Function<Object, T> converter) {
|
||||
super(name, internalName, converter);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import com.google.common.collect.Ordering;
|
|||
import com.google.common.collect.TreeMultimap;
|
||||
import org.apache.solr.cluster.*;
|
||||
import org.apache.solr.cluster.placement.*;
|
||||
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
|
||||
import org.apache.solr.common.util.Pair;
|
||||
import org.apache.solr.common.util.SuppressForbidden;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -194,7 +195,9 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
|
|||
|
||||
// Request all needed attributes
|
||||
attributeFetcher.requestNodeSystemProperty(AVAILABILITY_ZONE_SYSPROP).requestNodeSystemProperty(REPLICA_TYPE_SYSPROP);
|
||||
attributeFetcher.requestNodeCoreCount().requestNodeFreeDisk();
|
||||
attributeFetcher
|
||||
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
|
||||
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB);
|
||||
attributeFetcher.fetchFrom(nodes);
|
||||
final AttributeValues attrValues = attributeFetcher.fetchAttributes();
|
||||
|
||||
|
@ -306,21 +309,21 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
|
|||
|
||||
for (Node node : nodes) {
|
||||
// Exclude nodes with unknown or too small disk free space
|
||||
if (attrValues.getFreeDisk(node).isEmpty()) {
|
||||
if (attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).isEmpty()) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Unknown free disk on node {}, excluding it from placement decisions.", node.getName());
|
||||
}
|
||||
// We rely later on the fact that the free disk optional is present (see CoresAndDiskComparator), be careful it you change anything here.
|
||||
continue;
|
||||
}
|
||||
if (attrValues.getFreeDisk(node).get() < minimalFreeDiskGB) {
|
||||
if (attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).get() < minimalFreeDiskGB) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Node {} free disk ({}GB) lower than configured minimum {}GB, excluding it from placement decisions.", node.getName(), attrValues.getFreeDisk(node).get(), minimalFreeDiskGB);
|
||||
log.warn("Node {} free disk ({}GB) lower than configured minimum {}GB, excluding it from placement decisions.", node.getName(), attrValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB).get(), minimalFreeDiskGB);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attrValues.getCoresCount(node).isEmpty()) {
|
||||
if (attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).isEmpty()) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Unknown number of cores on node {}, excluding it from placement decisions.", node.getName());
|
||||
}
|
||||
|
@ -328,7 +331,7 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
|
|||
continue;
|
||||
}
|
||||
|
||||
Integer coresCount = attrValues.getCoresCount(node).get();
|
||||
Integer coresCount = attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).get();
|
||||
coresOnNodes.put(node, coresCount);
|
||||
|
||||
String supportedReplicaTypes = attrValues.getSystemProperty(node, REPLICA_TYPE_SYSPROP).isPresent() ? attrValues.getSystemProperty(node, REPLICA_TYPE_SYSPROP).get() : null;
|
||||
|
@ -552,8 +555,8 @@ public class AffinityPlacementFactory implements PlacementPluginFactory<Affinity
|
|||
@Override
|
||||
public int compare(Node a, Node b) {
|
||||
// Note all nodes do have free disk defined. This has been verified earlier.
|
||||
boolean aHasLowFreeSpace = attrValues.getFreeDisk(a).get() < prioritizedFreeDiskGB;
|
||||
boolean bHasLowFreeSpace = attrValues.getFreeDisk(b).get() < prioritizedFreeDiskGB;
|
||||
boolean aHasLowFreeSpace = attrValues.getNodeMetric(a, NodeMetricImpl.FREE_DISK_GB).get() < prioritizedFreeDiskGB;
|
||||
boolean bHasLowFreeSpace = attrValues.getNodeMetric(b, NodeMetricImpl.FREE_DISK_GB).get() < prioritizedFreeDiskGB;
|
||||
if (aHasLowFreeSpace != bHasLowFreeSpace) {
|
||||
// A node with low free space should be considered > node with high free space since it needs to come later in sort order
|
||||
return Boolean.compare(aHasLowFreeSpace, bHasLowFreeSpace);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.solr.cluster.Node;
|
|||
import org.apache.solr.cluster.Replica;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
import org.apache.solr.cluster.placement.*;
|
||||
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
|
||||
import org.apache.solr.common.util.SuppressForbidden;
|
||||
|
||||
/**
|
||||
|
@ -66,17 +67,17 @@ public class MinimizeCoresPlacementFactory implements PlacementPluginFactory<Pla
|
|||
|
||||
Set<Node> nodes = request.getTargetNodes();
|
||||
|
||||
attributeFetcher.requestNodeCoreCount();
|
||||
attributeFetcher.requestNodeMetric(NodeMetricImpl.NUM_CORES);
|
||||
attributeFetcher.fetchFrom(nodes);
|
||||
AttributeValues attrValues = attributeFetcher.fetchAttributes();
|
||||
|
||||
|
||||
// Get the number of cores on each node and sort the nodes by increasing number of cores
|
||||
for (Node node : nodes) {
|
||||
if (attrValues.getCoresCount(node).isEmpty()) {
|
||||
if (attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).isEmpty()) {
|
||||
throw new PlacementException("Can't get number of cores in " + node);
|
||||
}
|
||||
nodesByCores.put(attrValues.getCoresCount(node).get(), node);
|
||||
nodesByCores.put(attrValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES).get(), node);
|
||||
}
|
||||
|
||||
Set<ReplicaPlacement> replicaPlacements = new HashSet<>(totalReplicasPerShard * request.getShardNames().size());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@
|
|||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.placement.AttributeFetcher;
|
||||
import org.apache.solr.cluster.placement.AttributeValues;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -31,36 +30,6 @@ public class AttributeFetcherForTest implements AttributeFetcher {
|
|||
this.attributeValues = attributeValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeCoreCount() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeDiskType() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeFreeDisk() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeTotalDisk() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeHeapUsage() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeSystemLoadAverage() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeSystemProperty(String name) {
|
||||
return this;
|
||||
|
@ -72,7 +41,12 @@ public class AttributeFetcherForTest implements AttributeFetcher {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestNodeMetric(String metricName, NodeMetricRegistry registry) {
|
||||
public AttributeFetcher requestNodeMetric(NodeMetric<?> metric) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestCollectionMetrics(SolrCollection solrCollection, Set<ReplicaMetric<?>> metricNames) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -81,11 +55,6 @@ public class AttributeFetcherForTest implements AttributeFetcher {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeFetcher requestMetric(String scope, String metricName) {
|
||||
throw new UnsupportedOperationException("Not yet implemented...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeValues fetchAttributes() {
|
||||
return attributeValues;
|
||||
|
|
|
@ -20,6 +20,9 @@ package org.apache.solr.cluster.placement;
|
|||
import org.apache.solr.cluster.*;
|
||||
import org.apache.solr.cluster.placement.impl.AttributeFetcherImpl;
|
||||
import org.apache.solr.cluster.placement.impl.AttributeValuesImpl;
|
||||
import org.apache.solr.cluster.placement.impl.CollectionMetricsBuilder;
|
||||
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
|
||||
import org.apache.solr.cluster.placement.impl.ReplicaMetricImpl;
|
||||
import org.apache.solr.common.util.Pair;
|
||||
import org.junit.Assert;
|
||||
|
||||
|
@ -48,7 +51,10 @@ public class Builders {
|
|||
public ClusterBuilder initializeLiveNodes(int countNodes) {
|
||||
nodeBuilders = new LinkedList<>();
|
||||
for (int n = 0; n < countNodes; n++) {
|
||||
nodeBuilders.add(new NodeBuilder().setNodeName("node_" + n)); // Default name, can be changed
|
||||
NodeBuilder nodeBuilder = new NodeBuilder().setNodeName("node_" + n); // Default name, can be changed
|
||||
nodeBuilder.setTotalDiskGB(10000.0);
|
||||
nodeBuilder.setFreeDiskGB(5000.0);
|
||||
nodeBuilders.add(nodeBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -87,10 +93,9 @@ public class Builders {
|
|||
}
|
||||
|
||||
public AttributeFetcher buildAttributeFetcher() {
|
||||
Map<Node, Integer> nodeToCoreCount = new HashMap<>();
|
||||
Map<Node, Long> nodeToFreeDisk = new HashMap<>();
|
||||
Map<String, Map<Node, String>> sysprops = new HashMap<>();
|
||||
Map<String, Map<Node, Double>> metrics = new HashMap<>();
|
||||
Map<NodeMetric<?>, Map<Node, Object>> metrics = new HashMap<>();
|
||||
Map<String, CollectionMetrics> collectionMetrics = new HashMap<>();
|
||||
|
||||
// TODO And a few more missing and will be added...
|
||||
|
||||
|
@ -100,10 +105,16 @@ public class Builders {
|
|||
Node node = nodeBuilder.build();
|
||||
|
||||
if (nodeBuilder.getCoreCount() != null) {
|
||||
nodeToCoreCount.put(node, nodeBuilder.getCoreCount());
|
||||
metrics.computeIfAbsent(NodeMetricImpl.NUM_CORES, n -> new HashMap<>())
|
||||
.put(node, nodeBuilder.getCoreCount());
|
||||
}
|
||||
if (nodeBuilder.getFreeDiskGB() != null) {
|
||||
nodeToFreeDisk.put(node, nodeBuilder.getFreeDiskGB());
|
||||
metrics.computeIfAbsent(NodeMetricImpl.FREE_DISK_GB, n -> new HashMap<>())
|
||||
.put(node, nodeBuilder.getFreeDiskGB());
|
||||
}
|
||||
if (nodeBuilder.getTotalDiskGB() != null) {
|
||||
metrics.computeIfAbsent(NodeMetricImpl.TOTAL_DISK_GB, n -> new HashMap<>())
|
||||
.put(node, nodeBuilder.getTotalDiskGB());
|
||||
}
|
||||
if (nodeBuilder.getSysprops() != null) {
|
||||
nodeBuilder.getSysprops().forEach((name, value) -> {
|
||||
|
@ -119,7 +130,20 @@ public class Builders {
|
|||
}
|
||||
}
|
||||
|
||||
AttributeValues attributeValues = new AttributeValuesImpl(nodeToCoreCount, Map.of(), nodeToFreeDisk, Map.of(), Map.of(), Map.of(), sysprops, metrics);
|
||||
if (!collectionBuilders.isEmpty()) {
|
||||
Map<Node, Object> nodeToCoreCount = metrics.computeIfAbsent(NodeMetricImpl.NUM_CORES, n -> new HashMap<>());
|
||||
collectionBuilders.forEach(builder -> {
|
||||
collectionMetrics.put(builder.collectionName, builder.collectionMetricsBuilder.build());
|
||||
SolrCollection collection = builder.build();
|
||||
collection.iterator().forEachRemaining(shard ->
|
||||
shard.iterator().forEachRemaining(replica -> {
|
||||
nodeToCoreCount.compute(replica.getNode(), (node, count) ->
|
||||
(count == null) ? 1 : ((Number) count).intValue() + 1);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
AttributeValues attributeValues = new AttributeValuesImpl(sysprops, metrics, collectionMetrics);
|
||||
return new AttributeFetcherForTest(attributeValues);
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +153,7 @@ public class Builders {
|
|||
private LinkedList<ShardBuilder> shardBuilders = new LinkedList<>();
|
||||
private Map<String, String> customProperties = new HashMap<>();
|
||||
int replicaNumber = 0; // global replica numbering for the collection
|
||||
private CollectionMetricsBuilder collectionMetricsBuilder = new CollectionMetricsBuilder();
|
||||
|
||||
public CollectionBuilder(String collectionName) {
|
||||
this.collectionName = collectionName;
|
||||
|
@ -139,6 +164,10 @@ public class Builders {
|
|||
return this;
|
||||
}
|
||||
|
||||
public CollectionMetricsBuilder getCollectionMetricsBuilder() {
|
||||
return collectionMetricsBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The internal shards data structure to allow test code to modify the replica distribution to nodes.
|
||||
*/
|
||||
|
@ -222,18 +251,46 @@ public class Builders {
|
|||
* Initializes shard and replica builders for the collection based on passed parameters. Replicas are assigned round
|
||||
* robin to the nodes. The shard leader is the first NRT replica of each shard (or first TLOG is no NRT).
|
||||
* Shard and replica configuration can be modified afterwards, the returned builder hierarchy is a convenient starting point.
|
||||
* @param countShards number of shards to create
|
||||
* @param countNrtReplicas number of NRT replicas per shard
|
||||
* @param countTlogReplicas number of TLOG replicas per shard
|
||||
* @param countPullReplicas number of PULL replicas per shard
|
||||
* @param nodes list of nodes to place replicas on.
|
||||
*/
|
||||
public CollectionBuilder initializeShardsReplicas(int countShards, int countNrtReplicas, int countTlogReplicas,
|
||||
int countPullReplicas, List<NodeBuilder> nodes) {
|
||||
int countPullReplicas, List<NodeBuilder> nodes) {
|
||||
return initializeShardsReplicas(countShards, countNrtReplicas, countTlogReplicas, countPullReplicas, nodes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes shard and replica builders for the collection based on passed parameters. Replicas are assigned round
|
||||
* robin to the nodes. The shard leader is the first NRT replica of each shard (or first TLOG is no NRT).
|
||||
* Shard and replica configuration can be modified afterwards, the returned builder hierarchy is a convenient starting point.
|
||||
* @param countShards number of shards to create
|
||||
* @param countNrtReplicas number of NRT replicas per shard
|
||||
* @param countTlogReplicas number of TLOG replicas per shard
|
||||
* @param countPullReplicas number of PULL replicas per shard
|
||||
* @param nodes list of nodes to place replicas on.
|
||||
* @param initialSizeGBPerShard initial replica size (in GB) per shard
|
||||
*/
|
||||
public CollectionBuilder initializeShardsReplicas(int countShards, int countNrtReplicas, int countTlogReplicas,
|
||||
int countPullReplicas, List<NodeBuilder> nodes,
|
||||
List<Integer> initialSizeGBPerShard) {
|
||||
Iterator<NodeBuilder> nodeIterator = nodes.iterator();
|
||||
|
||||
shardBuilders = new LinkedList<>();
|
||||
if (initialSizeGBPerShard != null && initialSizeGBPerShard.size() != countShards) {
|
||||
throw new RuntimeException("list of shard sizes must be the same length as the countShards!");
|
||||
}
|
||||
|
||||
for (int shardNumber = 1; shardNumber <= countShards; shardNumber++) {
|
||||
String shardName = buildShardName(shardNumber);
|
||||
|
||||
CollectionMetricsBuilder.ShardMetricsBuilder shardMetricsBuilder = new CollectionMetricsBuilder.ShardMetricsBuilder(shardName);
|
||||
|
||||
LinkedList<ReplicaBuilder> replicas = new LinkedList<>();
|
||||
ReplicaBuilder leader = null;
|
||||
CollectionMetricsBuilder.ReplicaMetricsBuilder leaderMetrics = null;
|
||||
|
||||
// Iterate on requested counts, NRT then TLOG then PULL. Leader chosen as first NRT (or first TLOG if no NRT)
|
||||
List<Pair<Replica.ReplicaType, Integer>> replicaTypes = List.of(
|
||||
|
@ -258,15 +315,23 @@ public class Builders {
|
|||
.setReplicaState(Replica.ReplicaState.ACTIVE).setReplicaNode(node);
|
||||
replicas.add(replicaBuilder);
|
||||
|
||||
CollectionMetricsBuilder.ReplicaMetricsBuilder replicaMetricsBuilder = new CollectionMetricsBuilder.ReplicaMetricsBuilder(replicaName);
|
||||
shardMetricsBuilder.getReplicaMetricsBuilders().put(replicaName, replicaMetricsBuilder);
|
||||
if (initialSizeGBPerShard != null) {
|
||||
replicaMetricsBuilder.addMetric(ReplicaMetricImpl.INDEX_SIZE_GB, initialSizeGBPerShard.get(shardNumber - 1) * ReplicaMetricImpl.GB);
|
||||
}
|
||||
if (leader == null && type != Replica.ReplicaType.PULL) {
|
||||
leader = replicaBuilder;
|
||||
leaderMetrics = replicaMetricsBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShardBuilder shardBuilder = new ShardBuilder();
|
||||
shardBuilder.setShardName(shardName).setReplicaBuilders(replicas).setLeader(leader);
|
||||
shardMetricsBuilder.setLeaderMetrics(leaderMetrics);
|
||||
shardBuilders.add(shardBuilder);
|
||||
collectionMetricsBuilder.getShardMetricsBuilders().put(shardName, shardMetricsBuilder);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -353,6 +418,7 @@ public class Builders {
|
|||
private Replica.ReplicaType replicaType;
|
||||
private Replica.ReplicaState replicaState;
|
||||
private NodeBuilder replicaNode;
|
||||
private Map<ReplicaMetric<?>, Object> metrics;
|
||||
|
||||
public ReplicaBuilder setReplicaName(String replicaName) {
|
||||
this.replicaName = replicaName;
|
||||
|
@ -383,6 +449,14 @@ public class Builders {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ReplicaBuilder setReplicaMetric(ReplicaMetric<?> metric, Object value) {
|
||||
if (metrics == null) {
|
||||
metrics = new HashMap<>();
|
||||
}
|
||||
metrics.put(metric, metric.convert(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Replica build(Shard shard) {
|
||||
return new ClusterAbstractionsForTest.ReplicaImpl(replicaName, coreName, shard, replicaType, replicaState, replicaNode.build());
|
||||
}
|
||||
|
@ -391,9 +465,10 @@ public class Builders {
|
|||
public static class NodeBuilder {
|
||||
private String nodeName = null;
|
||||
private Integer coreCount = null;
|
||||
private Long freeDiskGB = null;
|
||||
private Double freeDiskGB = null;
|
||||
private Double totalDiskGB = null;
|
||||
private Map<String, String> sysprops = null;
|
||||
private Map<String, Double> metrics = null;
|
||||
private Map<NodeMetric<?>, Object> metrics = null;
|
||||
|
||||
public NodeBuilder setNodeName(String nodeName) {
|
||||
this.nodeName = nodeName;
|
||||
|
@ -405,11 +480,16 @@ public class Builders {
|
|||
return this;
|
||||
}
|
||||
|
||||
public NodeBuilder setFreeDiskGB(Long freeDiskGB) {
|
||||
public NodeBuilder setFreeDiskGB(Double freeDiskGB) {
|
||||
this.freeDiskGB = freeDiskGB;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NodeBuilder setTotalDiskGB(Double totalDiskGB) {
|
||||
this.totalDiskGB = totalDiskGB;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NodeBuilder setSysprop(String key, String value) {
|
||||
if (sysprops == null) {
|
||||
sysprops = new HashMap<>();
|
||||
|
@ -419,12 +499,11 @@ public class Builders {
|
|||
return this;
|
||||
}
|
||||
|
||||
public NodeBuilder setMetric(AttributeFetcher.NodeMetricRegistry registry, String key, Double value) {
|
||||
public NodeBuilder setMetric(NodeMetric<?> metric, Object value) {
|
||||
if (metrics == null) {
|
||||
metrics = new HashMap<>();
|
||||
}
|
||||
String name = AttributeFetcherImpl.getMetricSnitchTag(key, registry);
|
||||
metrics.put(name, value);
|
||||
metrics.put(metric, metric.convert(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -432,15 +511,19 @@ public class Builders {
|
|||
return coreCount;
|
||||
}
|
||||
|
||||
public Long getFreeDiskGB() {
|
||||
public Double getFreeDiskGB() {
|
||||
return freeDiskGB;
|
||||
}
|
||||
|
||||
public Double getTotalDiskGB() {
|
||||
return totalDiskGB;
|
||||
}
|
||||
|
||||
public Map<String, String> getSysprops() {
|
||||
return sysprops;
|
||||
}
|
||||
|
||||
public Map<String, Double> getMetrics() {
|
||||
public Map<NodeMetric<?>, Object> getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.solr.cluster.placement;
|
||||
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.cluster.Cluster;
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.Shard;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
import org.apache.solr.cluster.placement.impl.NodeMetricImpl;
|
||||
import org.apache.solr.cluster.placement.impl.ReplicaMetricImpl;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.solr.cluster.placement.Builders.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BuildersTest extends SolrTestCaseJ4 {
|
||||
|
||||
@Test
|
||||
public void testClusterBuilder() throws Exception {
|
||||
int NUM_NODES = 3;
|
||||
int NUM_SHARDS = 2;
|
||||
int NUM_NRT_REPLICAS = 2;
|
||||
String collectionName = "test";
|
||||
ClusterBuilder clusterBuilder = newClusterBuilder()
|
||||
.initializeLiveNodes(NUM_NODES);
|
||||
CollectionBuilder collectionBuilder = newCollectionBuilder(collectionName)
|
||||
.initializeShardsReplicas(NUM_SHARDS,
|
||||
NUM_NRT_REPLICAS,
|
||||
NUM_NRT_REPLICAS + 1,
|
||||
NUM_NRT_REPLICAS + 2,
|
||||
clusterBuilder.getLiveNodeBuilders(),
|
||||
List.of(10, 20));
|
||||
clusterBuilder.addCollection(collectionBuilder);
|
||||
Cluster cluster = clusterBuilder.build();
|
||||
assertEquals("number of nodes", NUM_NODES, cluster.getLiveNodes().size());
|
||||
SolrCollection collection = cluster.getCollection(collectionName);
|
||||
assertNotNull("collection", collection);
|
||||
assertEquals("shards", 2, collection.getShardNames().size());
|
||||
for (String shardName : collection.getShardNames()) {
|
||||
Shard shard = collection.getShard(shardName);
|
||||
assertNotNull("shard leader", shard.getLeader());
|
||||
int[] counts = new int[3];
|
||||
shard.iterator().forEachRemaining(r -> {
|
||||
switch (r.getType()) {
|
||||
case NRT:
|
||||
counts[0]++;
|
||||
break;
|
||||
case TLOG:
|
||||
counts[1]++;
|
||||
break;
|
||||
case PULL:
|
||||
counts[2]++;
|
||||
}
|
||||
});
|
||||
assertEquals("numNrt", NUM_NRT_REPLICAS, counts[0]);
|
||||
assertEquals("numTlog", NUM_NRT_REPLICAS + 1, counts[1]);
|
||||
assertEquals("numPull", NUM_NRT_REPLICAS + 2, counts[2]);
|
||||
}
|
||||
// AttributeFetcher
|
||||
AttributeFetcher attributeFetcher = clusterBuilder.buildAttributeFetcher();
|
||||
attributeFetcher
|
||||
.fetchFrom(cluster.getLiveNodes())
|
||||
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
|
||||
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB)
|
||||
.requestNodeMetric(NodeMetricImpl.TOTAL_DISK_GB)
|
||||
.requestCollectionMetrics(collection, Set.of(ReplicaMetricImpl.INDEX_SIZE_GB));
|
||||
AttributeValues attributeValues = attributeFetcher.fetchAttributes();
|
||||
for (Node node : cluster.getLiveNodes()) {
|
||||
Optional<Integer> coreCount = attributeValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES);
|
||||
assertTrue("coreCount present", coreCount.isPresent());
|
||||
Optional<Double> diskOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB);
|
||||
assertTrue("freeDisk", diskOpt.isPresent());
|
||||
diskOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.TOTAL_DISK_GB);
|
||||
assertTrue("totalDisk", diskOpt.isPresent());
|
||||
}
|
||||
Optional<CollectionMetrics> collectionMetricsOpt = attributeValues.getCollectionMetrics(collectionName);
|
||||
assertTrue("collectionMetrics present", collectionMetricsOpt.isPresent());
|
||||
CollectionMetrics collectionMetrics = collectionMetricsOpt.get();
|
||||
for (String shardName : collection.getShardNames()) {
|
||||
Optional<ShardMetrics> shardMetricsOpt = collectionMetrics.getShardMetrics(shardName);
|
||||
assertTrue("shard metrics", shardMetricsOpt.isPresent());
|
||||
ShardMetrics shardMetrics = shardMetricsOpt.get();
|
||||
Optional<ReplicaMetrics> replicaMetricsOpt = shardMetrics.getLeaderMetrics();
|
||||
assertTrue("leader metrics", replicaMetricsOpt.isPresent());
|
||||
ReplicaMetrics leaderMetrics = replicaMetricsOpt.get();
|
||||
Optional<Double> sizeOpt = leaderMetrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
|
||||
assertTrue("missing size", sizeOpt.isPresent());
|
||||
if (shardName.endsWith("1")) {
|
||||
assertEquals("size", 10, ((Number) sizeOpt.get()).intValue());
|
||||
} else {
|
||||
assertEquals("size", 20, ((Number) sizeOpt.get()).intValue());
|
||||
}
|
||||
Shard shard = collection.getShard(shardName);
|
||||
shard.iterator().forEachRemaining(r -> {
|
||||
Optional<ReplicaMetrics> metricsOpt = shardMetrics.getReplicaMetrics(r.getReplicaName());
|
||||
assertTrue("replica metrics", metricsOpt.isPresent());
|
||||
ReplicaMetrics metrics = metricsOpt.get();
|
||||
Optional<Double> replicaSizeOpt = metrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
|
||||
assertTrue("missing size", replicaSizeOpt.isPresent());
|
||||
if (shardName.endsWith("1")) {
|
||||
assertEquals("size", 10, ((Number) replicaSizeOpt.get()).intValue());
|
||||
} else {
|
||||
assertEquals("size", 20, ((Number) replicaSizeOpt.get()).intValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,8 +24,17 @@ import org.apache.solr.client.solrj.request.beans.PluginMeta;
|
|||
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
|
||||
import org.apache.solr.client.solrj.response.V2Response;
|
||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
||||
import org.apache.solr.cluster.Cluster;
|
||||
import org.apache.solr.cluster.Node;
|
||||
import org.apache.solr.cluster.SolrCollection;
|
||||
import org.apache.solr.cluster.placement.AttributeFetcher;
|
||||
import org.apache.solr.cluster.placement.AttributeValues;
|
||||
import org.apache.solr.cluster.placement.CollectionMetrics;
|
||||
import org.apache.solr.cluster.placement.NodeMetric;
|
||||
import org.apache.solr.cluster.placement.PlacementPluginConfig;
|
||||
import org.apache.solr.cluster.placement.PlacementPluginFactory;
|
||||
import org.apache.solr.cluster.placement.ReplicaMetrics;
|
||||
import org.apache.solr.cluster.placement.ShardMetrics;
|
||||
import org.apache.solr.cluster.placement.plugins.AffinityPlacementConfig;
|
||||
import org.apache.solr.cluster.placement.plugins.AffinityPlacementFactory;
|
||||
import org.apache.solr.cloud.MiniSolrCloudCluster;
|
||||
|
@ -47,6 +56,8 @@ import java.lang.invoke.MethodHandles;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -220,6 +231,80 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
|
|||
assertNull("no factory should be present", factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributeFetcherImpl() throws Exception {
|
||||
CollectionAdminResponse rsp = CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
|
||||
.process(cluster.getSolrClient());
|
||||
assertTrue(rsp.isSuccess());
|
||||
cluster.waitForActiveCollection(COLLECTION, 2, 4);
|
||||
Cluster cluster = new SimpleClusterAbstractionsImpl.ClusterImpl(cloudManager);
|
||||
SolrCollection collection = cluster.getCollection(COLLECTION);
|
||||
AttributeFetcher attributeFetcher = new AttributeFetcherImpl(cloudManager);
|
||||
NodeMetric<String> someMetricKey = new NodeMetricImpl<>("solr.jvm:system.properties:user.name");
|
||||
String sysprop = "user.name";
|
||||
String sysenv = "PWD";
|
||||
attributeFetcher
|
||||
.fetchFrom(cluster.getLiveNodes())
|
||||
.requestNodeMetric(NodeMetricImpl.HEAP_USAGE)
|
||||
.requestNodeMetric(NodeMetricImpl.SYSLOAD_AVG)
|
||||
.requestNodeMetric(NodeMetricImpl.NUM_CORES)
|
||||
.requestNodeMetric(NodeMetricImpl.FREE_DISK_GB)
|
||||
.requestNodeMetric(NodeMetricImpl.TOTAL_DISK_GB)
|
||||
.requestNodeMetric(NodeMetricImpl.AVAILABLE_PROCESSORS)
|
||||
.requestNodeMetric(someMetricKey)
|
||||
.requestNodeSystemProperty(sysprop)
|
||||
.requestNodeEnvironmentVariable(sysenv)
|
||||
.requestCollectionMetrics(collection, Set.of(ReplicaMetricImpl.INDEX_SIZE_GB, ReplicaMetricImpl.QUERY_RATE_1MIN, ReplicaMetricImpl.UPDATE_RATE_1MIN));
|
||||
AttributeValues attributeValues = attributeFetcher.fetchAttributes();
|
||||
String userName = System.getProperty("user.name");
|
||||
String pwd = System.getenv("PWD");
|
||||
// node metrics
|
||||
for (Node node : cluster.getLiveNodes()) {
|
||||
Optional<Double> doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.HEAP_USAGE);
|
||||
assertTrue("heap usage", doubleOpt.isPresent());
|
||||
assertTrue("heap usage should be 0 < heapUsage < 100 but was " + doubleOpt, doubleOpt.get() > 0 && doubleOpt.get() < 100);
|
||||
doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.TOTAL_DISK_GB);
|
||||
assertTrue("total disk", doubleOpt.isPresent());
|
||||
assertTrue("total disk should be > 0 but was " + doubleOpt, doubleOpt.get() > 0);
|
||||
doubleOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.FREE_DISK_GB);
|
||||
assertTrue("free disk", doubleOpt.isPresent());
|
||||
assertTrue("free disk should be > 0 but was " + doubleOpt, doubleOpt.get() > 0);
|
||||
Optional<Integer> intOpt = attributeValues.getNodeMetric(node, NodeMetricImpl.NUM_CORES);
|
||||
assertTrue("cores", intOpt.isPresent());
|
||||
assertTrue("cores should be > 0", intOpt.get() > 0);
|
||||
assertTrue("systemLoadAverage 2", attributeValues.getNodeMetric(node, NodeMetricImpl.SYSLOAD_AVG).isPresent());
|
||||
assertTrue("availableProcessors", attributeValues.getNodeMetric(node, NodeMetricImpl.AVAILABLE_PROCESSORS).isPresent());
|
||||
Optional<String> userNameOpt = attributeValues.getNodeMetric(node, someMetricKey);
|
||||
assertTrue("user.name", userNameOpt.isPresent());
|
||||
assertEquals("userName", userName, userNameOpt.get());
|
||||
Optional<String> syspropOpt = attributeValues.getSystemProperty(node, sysprop);
|
||||
assertTrue("sysprop", syspropOpt.isPresent());
|
||||
assertEquals("user.name sysprop", userName, syspropOpt.get());
|
||||
Optional<String> sysenvOpt = attributeValues.getEnvironmentVariable(node, sysenv);
|
||||
assertTrue("sysenv", sysenvOpt.isPresent());
|
||||
assertEquals("PWD sysenv", pwd, sysenvOpt.get());
|
||||
}
|
||||
assertTrue(attributeValues.getCollectionMetrics(COLLECTION).isPresent());
|
||||
CollectionMetrics collectionMetrics = attributeValues.getCollectionMetrics(COLLECTION).get();
|
||||
collection.shards().forEach(shard -> {
|
||||
Optional<ShardMetrics> shardMetricsOpt = collectionMetrics.getShardMetrics(shard.getShardName());
|
||||
assertTrue("shard metrics", shardMetricsOpt.isPresent());
|
||||
shard.replicas().forEach(replica -> {
|
||||
Optional<ReplicaMetrics> replicaMetricsOpt = shardMetricsOpt.get().getReplicaMetrics(replica.getReplicaName());
|
||||
assertTrue("replica metrics", replicaMetricsOpt.isPresent());
|
||||
ReplicaMetrics replicaMetrics = replicaMetricsOpt.get();
|
||||
Optional<Double> indexSizeOpt = replicaMetrics.getReplicaMetric(ReplicaMetricImpl.INDEX_SIZE_GB);
|
||||
assertTrue("indexSize", indexSizeOpt.isPresent());
|
||||
assertTrue("wrong type, expected Double but was " + indexSizeOpt.get().getClass(), indexSizeOpt.get() instanceof Double);
|
||||
assertTrue("indexSize should be > 0 but was " + indexSizeOpt.get(), indexSizeOpt.get() > 0);
|
||||
assertTrue("indexSize should be < 0.01 but was " + indexSizeOpt.get(), indexSizeOpt.get() < 0.01);
|
||||
|
||||
assertNotNull("queryRate", replicaMetrics.getReplicaMetric(ReplicaMetricImpl.QUERY_RATE_1MIN));
|
||||
assertNotNull("updateRate", replicaMetrics.getReplicaMetric(ReplicaMetricImpl.UPDATE_RATE_1MIN));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private int waitForVersionChange(int currentVersion, DelegatingPlacementPluginFactory wrapper, int timeoutSec) throws Exception {
|
||||
TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, TimeSource.NANO_TIME);
|
||||
|
||||
|
|
|
@ -79,8 +79,8 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
|
||||
Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(2);
|
||||
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
|
||||
nodeBuilders.get(0).setCoreCount(1).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
nodeBuilders.get(1).setCoreCount(10).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
nodeBuilders.get(0).setCoreCount(1).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
|
||||
nodeBuilders.get(1).setCoreCount(10).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
|
||||
|
||||
Builders.CollectionBuilder collectionBuilder = Builders.newCollectionBuilder(collectionName);
|
||||
|
||||
|
@ -126,11 +126,11 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
|
||||
for (int i = 0; i < nodeBuilders.size(); i++) {
|
||||
if (i == LOW_SPACE_NODE_INDEX) {
|
||||
nodeBuilders.get(i).setCoreCount(1).setFreeDiskGB(MINIMAL_FREE_DISK_GB + 1); // Low space
|
||||
nodeBuilders.get(i).setCoreCount(1).setFreeDiskGB((double)(MINIMAL_FREE_DISK_GB + 1)); // Low space
|
||||
} else if (i == NO_SPACE_NODE_INDEX) {
|
||||
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(1L); // Really not enough space
|
||||
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(1.0); // Really not enough space
|
||||
} else {
|
||||
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
nodeBuilders.get(i).setCoreCount(10).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
|
||||
}
|
||||
}
|
||||
List<Node> liveNodes = clusterBuilder.buildLiveNodes();
|
||||
|
@ -190,7 +190,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
|
||||
int coresOnNode = 10;
|
||||
for (Builders.NodeBuilder nodeBuilder : nodeBuilders) {
|
||||
nodeBuilder.setCoreCount(coresOnNode).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
nodeBuilder.setCoreCount(coresOnNode).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
|
||||
coresOnNode += 10;
|
||||
}
|
||||
|
||||
|
@ -262,7 +262,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
for (int i = 0; i < 9; i++) {
|
||||
final String az;
|
||||
final int numcores;
|
||||
final long freedisk;
|
||||
final double freedisk;
|
||||
final String acceptedReplicaType;
|
||||
|
||||
if (i == AZ1_NRT_LOWCORES || i == AZ1_NRT_HIGHCORES || i == AZ1_TLOGPULL_LOWFREEDISK) {
|
||||
|
@ -347,7 +347,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
for (int i = 0; i < 9; i++) {
|
||||
nodeBuilders.get(i).setSysprop(AffinityPlacementFactory.AVAILABILITY_ZONE_SYSPROP, "AZ" + (i / 3))
|
||||
.setCoreCount(i)
|
||||
.setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
.setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 10));
|
||||
}
|
||||
|
||||
// The collection does not exist, has 1 shard.
|
||||
|
@ -390,7 +390,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
|
||||
int coreCount = 0;
|
||||
for (Builders.NodeBuilder nodeBuilder : nodeBuilders) {
|
||||
nodeBuilder.setCoreCount(coreCount++).setFreeDiskGB(PRIORITIZED_FREE_DISK_GB + 1);
|
||||
nodeBuilder.setCoreCount(coreCount++).setFreeDiskGB((double)(PRIORITIZED_FREE_DISK_GB + 1));
|
||||
}
|
||||
|
||||
// The collection already exists with shards and replicas
|
||||
|
@ -493,7 +493,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
for (int i = 0; i < NUM_NODES; i++) {
|
||||
Builders.NodeBuilder nodeBuilder = clusterBuilder.getLiveNodeBuilders().get(i);
|
||||
nodeBuilder.setCoreCount(0);
|
||||
nodeBuilder.setFreeDiskGB(100L);
|
||||
nodeBuilder.setFreeDiskGB(100.0);
|
||||
if (i < NUM_NODES / 2) {
|
||||
nodeBuilder.setSysprop(AffinityPlacementFactory.AVAILABILITY_ZONE_SYSPROP, "az1");
|
||||
} else {
|
||||
|
@ -551,7 +551,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
for (int i = 0; i < NUM_NODES; i++) {
|
||||
Builders.NodeBuilder nodeBuilder = clusterBuilder.getLiveNodeBuilders().get(i);
|
||||
nodeBuilder.setCoreCount(0);
|
||||
nodeBuilder.setFreeDiskGB(100L);
|
||||
nodeBuilder.setFreeDiskGB(100.0);
|
||||
if (i < NUM_NODES / 3 * 2) {
|
||||
nodeBuilder.setSysprop(AffinityPlacementFactory.REPLICA_TYPE_SYSPROP, "Nrt, TlOg");
|
||||
nodeBuilder.setSysprop("group", "one");
|
||||
|
@ -621,10 +621,10 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
nodeBuilder.setCoreCount(0);
|
||||
if (i == 0) {
|
||||
// default minimalFreeDiskGB == 20
|
||||
nodeBuilder.setFreeDiskGB(1L);
|
||||
nodeBuilder.setFreeDiskGB(1.0);
|
||||
smallNode = nodeBuilder.build();
|
||||
} else {
|
||||
nodeBuilder.setFreeDiskGB(100L);
|
||||
nodeBuilder.setFreeDiskGB(100.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,7 +678,7 @@ public class AffinityPlacementFactoryTest extends SolrTestCaseJ4 {
|
|||
Builders.ClusterBuilder clusterBuilder = Builders.newClusterBuilder().initializeLiveNodes(numNodes);
|
||||
LinkedList<Builders.NodeBuilder> nodeBuilders = clusterBuilder.getLiveNodeBuilders();
|
||||
for (int i = 0; i < numNodes; i++) {
|
||||
nodeBuilders.get(i).setCoreCount(0).setFreeDiskGB(Long.valueOf(numNodes));
|
||||
nodeBuilders.get(i).setCoreCount(0).setFreeDiskGB((double) numNodes);
|
||||
}
|
||||
|
||||
Builders.CollectionBuilder collectionBuilder = Builders.newCollectionBuilder(collectionName);
|
||||
|
|
|
@ -63,10 +63,6 @@ import static java.util.Collections.emptyMap;
|
|||
public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter {
|
||||
public static final String METRICS_PREFIX = "metrics:";
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
//only for debugging
|
||||
public static SolrClientNodeStateProvider INST;
|
||||
|
||||
|
||||
|
||||
private final CloudSolrClient solrClient;
|
||||
protected final Map<String, Map<String, Map<String, List<Replica>>>> nodeVsCollectionVsShardVsReplicaInfo = new HashMap<>();
|
||||
|
@ -81,7 +77,6 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
|
|||
} catch (IOException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
|
||||
}
|
||||
if(log.isDebugEnabled()) INST = this;
|
||||
}
|
||||
|
||||
protected ClusterStateProvider getClusterStateProvider() {
|
||||
|
@ -167,28 +162,30 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
|
|||
}
|
||||
|
||||
protected Map<String, Object> fetchReplicaMetrics(String node, Map<String, Pair<String, Replica>> metricsKeyVsTagReplica) {
|
||||
Map<String, Object> collect = metricsKeyVsTagReplica.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getKey));
|
||||
Map<String, Set<Object>> collect = metricsKeyVsTagReplica.entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> Set.of(e.getKey())));
|
||||
ClientSnitchCtx ctx = new ClientSnitchCtx(null, null, emptyMap(), solrClient);
|
||||
fetchReplicaMetrics(node, ctx, collect);
|
||||
return ctx.getTags();
|
||||
|
||||
}
|
||||
|
||||
static void fetchReplicaMetrics(String solrNode, ClientSnitchCtx ctx, Map<String, Object> metricsKeyVsTag) {
|
||||
static void fetchReplicaMetrics(String solrNode, ClientSnitchCtx ctx, Map<String, Set<Object>> metricsKeyVsTag) {
|
||||
if (!ctx.isNodeAlive(solrNode)) return;
|
||||
ModifiableSolrParams params = new ModifiableSolrParams();
|
||||
params.add("key", metricsKeyVsTag.keySet().toArray(new String[0]));
|
||||
try {
|
||||
SimpleSolrResponse rsp = ctx.invokeWithRetry(solrNode, CommonParams.METRICS_PATH, params);
|
||||
metricsKeyVsTag.forEach((key, tag) -> {
|
||||
metricsKeyVsTag.forEach((key, tags) -> {
|
||||
Object v = Utils.getObjectByPath(rsp.nl, true, Arrays.asList("metrics", key));
|
||||
if (tag instanceof Function) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
Pair<String, Object> p = (Pair<String, Object>) ((Function) tag).apply(v);
|
||||
ctx.getTags().put(p.first(), p.second());
|
||||
} else {
|
||||
if (v != null) ctx.getTags().put(tag.toString(), v);
|
||||
for (Object tag : tags) {
|
||||
if (tag instanceof Function) {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
Pair<String, Object> p = (Pair<String, Object>) ((Function) tag).apply(v);
|
||||
ctx.getTags().put(p.first(), p.second());
|
||||
} else {
|
||||
if (v != null) ctx.getTags().put(tag.toString(), v);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
|
@ -207,26 +204,19 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
|
|||
protected void getRemoteInfo(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
|
||||
if (!((ClientSnitchCtx)ctx).isNodeAlive(solrNode)) return;
|
||||
ClientSnitchCtx snitchContext = (ClientSnitchCtx) ctx;
|
||||
Map<String, Object> metricsKeyVsTag = new HashMap<>();
|
||||
Map<String, Set<Object>> metricsKeyVsTag = new HashMap<>();
|
||||
for (String tag : requestedTags) {
|
||||
if (tag.startsWith(SYSPROP)) {
|
||||
metricsKeyVsTag.put("solr.jvm:system.properties:" + tag.substring(SYSPROP.length()), tag);
|
||||
metricsKeyVsTag.computeIfAbsent("solr.jvm:system.properties:" + tag.substring(SYSPROP.length()), k -> new HashSet<>())
|
||||
.add(tag);
|
||||
} else if (tag.startsWith(SYSENV)) {
|
||||
metricsKeyVsTag.computeIfAbsent("solr.jvm:system.env:" + tag.substring(SYSENV.length()), k -> new HashSet<>())
|
||||
.add(tag);
|
||||
} else if (tag.startsWith(METRICS_PREFIX)) {
|
||||
metricsKeyVsTag.put(tag.substring(METRICS_PREFIX.length()), tag);
|
||||
metricsKeyVsTag.computeIfAbsent(tag.substring(METRICS_PREFIX.length()), k -> new HashSet<>())
|
||||
.add(tag);
|
||||
}
|
||||
}
|
||||
if (requestedTags.contains(ImplicitSnitch.DISKTYPE)) {
|
||||
metricsKeyVsTag.put("solr.node:CONTAINER.fs.coreRoot.spins", (Function<Object, Pair<String, Object>>) o -> {
|
||||
if("true".equals(String.valueOf(o))){
|
||||
return new Pair<>(ImplicitSnitch.DISKTYPE, "rotational");
|
||||
}
|
||||
if("false".equals(String.valueOf(o))){
|
||||
return new Pair<>(ImplicitSnitch.DISKTYPE, "ssd");
|
||||
}
|
||||
return new Pair<>(ImplicitSnitch.DISKTYPE,null);
|
||||
|
||||
});
|
||||
}
|
||||
if (!metricsKeyVsTag.isEmpty()) {
|
||||
fetchReplicaMetrics(solrNode, snitchContext, metricsKeyVsTag);
|
||||
}
|
||||
|
@ -283,7 +273,7 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
|
|||
}
|
||||
if (requestedTags.contains(SYSLOADAVG)) {
|
||||
Number n = (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/os.systemLoadAverage");
|
||||
if (n != null) ctx.getTags().put(SYSLOADAVG, n.doubleValue() * 100.0d);
|
||||
if (n != null) ctx.getTags().put(SYSLOADAVG, n.doubleValue());
|
||||
}
|
||||
if (requestedTags.contains(HEAPUSAGE)) {
|
||||
Number n = (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/memory.heap.usage");
|
||||
|
|
|
@ -49,11 +49,11 @@ public class ImplicitSnitch extends Snitch {
|
|||
public static final String ROLE = "role";
|
||||
public static final String NODEROLE = "nodeRole";
|
||||
public static final String SYSPROP = "sysprop.";
|
||||
public static final String SYSENV = "sysenv.";
|
||||
public static final String SYSLOADAVG = "sysLoadAvg";
|
||||
public static final String HEAPUSAGE = "heapUsage";
|
||||
public static final String DISKTYPE = "diskType";
|
||||
public static final List<String> IP_SNITCHES = Collections.unmodifiableList(Arrays.asList("ip_1", "ip_2", "ip_3", "ip_4"));
|
||||
public static final Set<String> tags = Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, "ip_1", "ip_2", "ip_3", "ip_4");
|
||||
public static final Set<String> tags = Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, HEAPUSAGE, "ip_1", "ip_2", "ip_3", "ip_4");
|
||||
|
||||
@Override
|
||||
public void getTags(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
|
||||
|
|
Loading…
Reference in New Issue