You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by is...@apache.org on 2017/03/12 00:18:42 UTC
[33/50] [abbrv] lucene-solr:jira/solr-6736: SOLR-9858: Collect
aggregated metrics from nodes and shard leaders in overseer.
SOLR-9858: Collect aggregated metrics from nodes and shard leaders in overseer.
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/4d7bc947
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/4d7bc947
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/4d7bc947
Branch: refs/heads/jira/solr-6736
Commit: 4d7bc9477144937335e997ad630c4b89f558ddc5
Parents: a6e14ec
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Tue Mar 7 22:00:38 2017 +0100
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Tue Mar 7 22:01:21 2017 +0100
----------------------------------------------------------------------
solr/CHANGES.txt | 4 +
.../org/apache/solr/cloud/ElectionContext.java | 5 +-
.../java/org/apache/solr/cloud/Overseer.java | 7 +-
.../solr/cloud/OverseerNodePrioritizer.java | 2 +-
.../solr/cloud/OverseerTaskProcessor.java | 6 +-
.../org/apache/solr/cloud/ZkController.java | 2 +-
.../org/apache/solr/core/CoreContainer.java | 30 +-
.../org/apache/solr/core/JmxMonitoredMap.java | 9 +-
.../src/java/org/apache/solr/core/SolrCore.java | 4 +-
.../org/apache/solr/core/SolrInfoMBean.java | 4 +-
.../org/apache/solr/core/SolrXmlConfig.java | 3 +-
.../handler/admin/MetricsCollectorHandler.java | 228 +++++++++++
.../solr/handler/admin/MetricsHandler.java | 2 +-
.../apache/solr/metrics/AggregateMetric.java | 200 ++++++++++
.../solr/metrics/SolrCoreMetricManager.java | 125 +++++-
.../apache/solr/metrics/SolrMetricManager.java | 325 ++++++++++++++-
.../metrics/reporters/JmxObjectNameFactory.java | 6 +-
.../reporters/solr/SolrClusterReporter.java | 277 +++++++++++++
.../metrics/reporters/solr/SolrReporter.java | 392 +++++++++++++++++++
.../reporters/solr/SolrShardReporter.java | 188 +++++++++
.../metrics/reporters/solr/package-info.java | 22 ++
.../java/org/apache/solr/update/PeerSync.java | 8 +-
.../org/apache/solr/util/stats/MetricUtils.java | 265 +++++++++----
.../src/test-files/solr/solr-solrreporter.xml | 66 ++++
.../apache/solr/cloud/TestCloudRecovery.java | 6 +-
.../apache/solr/core/TestJmxMonitoredMap.java | 2 +-
.../solr/metrics/SolrCoreMetricManagerTest.java | 31 +-
.../solr/metrics/SolrMetricManagerTest.java | 30 +-
.../metrics/SolrMetricsIntegrationTest.java | 15 +-
.../metrics/reporters/SolrJmxReporterTest.java | 13 +-
.../reporters/solr/SolrCloudReportersTest.java | 163 ++++++++
.../reporters/solr/SolrShardReporterTest.java | 117 ++++++
.../apache/solr/util/stats/MetricUtilsTest.java | 54 ++-
.../client/solrj/impl/BinaryRequestWriter.java | 4 +-
.../solr/client/solrj/io/SolrClientCache.java | 26 +-
.../client/solrj/request/TestCoreAdmin.java | 4 +-
36 files changed, 2435 insertions(+), 210 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index dc97456..0e78535 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -50,6 +50,10 @@ Upgrading from Solr 6.x
factors should be indexed in a separate field and combined with the query
score using a function query.
+New Features
+----------------------
+* SOLR-9857, SOLR-9858: Collect aggregated metrics from nodes and shard leaders in overseer. (ab)
+
Bug Fixes
----------------------
* SOLR-9262: Connection and read timeouts are being ignored by UpdateShardHandler after SOLR-4509.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java b/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
index ff6fb30..d3ad322 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
@@ -714,14 +714,13 @@ final class OverseerElectionContext extends ElectionContext {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final SolrZkClient zkClient;
private Overseer overseer;
- public static final String OVERSEER_ELECT = "/overseer_elect";
public OverseerElectionContext(SolrZkClient zkClient, Overseer overseer, final String zkNodeName) {
- super(zkNodeName, OVERSEER_ELECT, OVERSEER_ELECT + "/leader", null, zkClient);
+ super(zkNodeName, Overseer.OVERSEER_ELECT, Overseer.OVERSEER_ELECT + "/leader", null, zkClient);
this.overseer = overseer;
this.zkClient = zkClient;
try {
- new ZkCmdExecutor(zkClient.getZkClientTimeout()).ensureExists(OVERSEER_ELECT, zkClient);
+ new ZkCmdExecutor(zkClient.getZkClientTimeout()).ensureExists(Overseer.OVERSEER_ELECT, zkClient);
} catch (KeeperException e) {
throw new SolrException(ErrorCode.SERVER_ERROR, e);
} catch (InterruptedException e) {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/cloud/Overseer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/Overseer.java b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
index 3a8aa3e..61f15fc 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
@@ -65,7 +65,8 @@ public class Overseer implements Closeable {
public static final int STATE_UPDATE_DELAY = 1500; // delay between cloud state updates
public static final int NUM_RESPONSES_TO_STORE = 10000;
-
+ public static final String OVERSEER_ELECT = "/overseer_elect";
+
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
enum LeaderStatus {DONT_KNOW, NO, YES}
@@ -281,7 +282,7 @@ public class Overseer implements Closeable {
private void checkIfIamStillLeader() {
if (zkController != null && zkController.getCoreContainer().isShutDown()) return;//shutting down no need to go further
org.apache.zookeeper.data.Stat stat = new org.apache.zookeeper.data.Stat();
- String path = OverseerElectionContext.OVERSEER_ELECT + "/leader";
+ String path = OVERSEER_ELECT + "/leader";
byte[] data;
try {
data = zkClient.getData(path, null, stat, true);
@@ -394,7 +395,7 @@ public class Overseer implements Closeable {
boolean success = true;
try {
ZkNodeProps props = ZkNodeProps.load(zkClient.getData(
- OverseerElectionContext.OVERSEER_ELECT + "/leader", null, null, true));
+ OVERSEER_ELECT + "/leader", null, null, true));
if (myId.equals(props.getStr("id"))) {
return LeaderStatus.YES;
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
index 6512d26..798eca3 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerNodePrioritizer.java
@@ -65,7 +65,7 @@ public class OverseerNodePrioritizer {
String ldr = OverseerTaskProcessor.getLeaderNode(zk);
if(overseerDesignates.contains(ldr)) return;
log.info("prioritizing overseer nodes at {} overseer designates are {}", overseerId, overseerDesignates);
- List<String> electionNodes = OverseerTaskProcessor.getSortedElectionNodes(zk, OverseerElectionContext.OVERSEER_ELECT + LeaderElector.ELECTION_NODE);
+ List<String> electionNodes = OverseerTaskProcessor.getSortedElectionNodes(zk, Overseer.OVERSEER_ELECT + LeaderElector.ELECTION_NODE);
if(electionNodes.size()<2) return;
log.info("sorted nodes {}", electionNodes);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
index ad53346..bed71a6 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
@@ -337,7 +337,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
public static List<String> getSortedOverseerNodeNames(SolrZkClient zk) throws KeeperException, InterruptedException {
List<String> children = null;
try {
- children = zk.getChildren(OverseerElectionContext.OVERSEER_ELECT + LeaderElector.ELECTION_NODE, null, true);
+ children = zk.getChildren(Overseer.OVERSEER_ELECT + LeaderElector.ELECTION_NODE, null, true);
} catch (Exception e) {
log.warn("error ", e);
return new ArrayList<>();
@@ -370,7 +370,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
public static String getLeaderId(SolrZkClient zkClient) throws KeeperException,InterruptedException{
byte[] data = null;
try {
- data = zkClient.getData(OverseerElectionContext.OVERSEER_ELECT + "/leader", null, new Stat(), true);
+ data = zkClient.getData(Overseer.OVERSEER_ELECT + "/leader", null, new Stat(), true);
} catch (KeeperException.NoNodeException e) {
return null;
}
@@ -384,7 +384,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
boolean success = true;
try {
ZkNodeProps props = ZkNodeProps.load(zkStateReader.getZkClient().getData(
- OverseerElectionContext.OVERSEER_ELECT + "/leader", null, null, true));
+ Overseer.OVERSEER_ELECT + "/leader", null, null, true));
if (myId.equals(props.getStr("id"))) {
return LeaderStatus.YES;
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/cloud/ZkController.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index c083736..333acd4 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -1715,7 +1715,7 @@ public class ZkController {
//however delete it . This is possible when the last attempt at deleting the election node failed.
if (electionNode.startsWith(getNodeName())) {
try {
- zkClient.delete(OverseerElectionContext.OVERSEER_ELECT + LeaderElector.ELECTION_NODE + "/" + electionNode, -1, true);
+ zkClient.delete(Overseer.OVERSEER_ELECT + LeaderElector.ELECTION_NODE + "/" + electionNode, -1, true);
} catch (NoNodeException e) {
//no problem
} catch (InterruptedException e) {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index e3977d7..b9597ae 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -69,6 +69,7 @@ import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.handler.admin.ConfigSetsHandler;
import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.handler.admin.InfoHandler;
+import org.apache.solr.handler.admin.MetricsCollectorHandler;
import org.apache.solr.handler.admin.MetricsHandler;
import org.apache.solr.handler.admin.SecurityConfHandler;
import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
@@ -177,6 +178,8 @@ public class CoreContainer {
protected MetricsHandler metricsHandler;
+ protected MetricsCollectorHandler metricsCollectorHandler;
+
private enum CoreInitFailedAction { fromleader, none }
/**
@@ -511,15 +514,18 @@ public class CoreContainer {
coreAdminHandler = createHandler(CORES_HANDLER_PATH, cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
configSetsHandler = createHandler(CONFIGSETS_HANDLER_PATH, cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class);
metricsHandler = createHandler(METRICS_PATH, MetricsHandler.class.getName(), MetricsHandler.class);
+ metricsCollectorHandler = createHandler(MetricsCollectorHandler.HANDLER_PATH, MetricsCollectorHandler.class.getName(), MetricsCollectorHandler.class);
+ // may want to add some configuration here in the future
+ metricsCollectorHandler.init(null);
containerHandlers.put(AUTHZ_PATH, securityConfHandler);
securityConfHandler.initializeMetrics(metricManager, SolrInfoMBean.Group.node.toString(), AUTHZ_PATH);
containerHandlers.put(AUTHC_PATH, securityConfHandler);
if(pkiAuthenticationPlugin != null)
containerHandlers.put(PKIAuthenticationPlugin.PATH, pkiAuthenticationPlugin.getRequestHandler());
- metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.node);
- metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.jvm);
- metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.jetty);
+ metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoMBean.Group.node);
+ metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoMBean.Group.jvm);
+ metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoMBean.Group.jetty);
coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController);
@@ -537,6 +543,10 @@ public class CoreContainer {
metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
unloadedCores, true, "unloaded",SolrInfoMBean.Category.CONTAINER.toString(), "cores");
+ if (isZooKeeperAware()) {
+ metricManager.loadClusterReporters(cfg.getMetricReporterPlugins(), this);
+ }
+
// setup executor to load cores in parallel
ExecutorService coreLoadExecutor = MetricUtils.instrumentedExecutorService(
ExecutorUtil.newMDCAwareFixedThreadPool(
@@ -660,10 +670,16 @@ public class CoreContainer {
isShutDown = true;
ExecutorUtil.shutdownAndAwaitTermination(coreContainerWorkExecutor);
+ if (metricManager != null) {
+ metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
+ }
if (isZooKeeperAware()) {
cancelCoreRecoveries();
- zkSys.zkController.publishNodeAsDown(zkSys.zkController.getNodeName());
+ zkSys.zkController.publishNodeAsDown(zkSys.zkController.getNodeName());
+ if (metricManager != null) {
+ metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.cluster));
+ }
}
try {
@@ -722,10 +738,6 @@ public class CoreContainer {
}
}
- if (metricManager != null) {
- metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
- }
-
// It should be safe to close the authorization plugin at this point.
try {
if(authorizationPlugin != null) {
@@ -1232,7 +1244,7 @@ public class CoreContainer {
try (SolrCore core = getCore(name)) {
if (core != null) {
String oldRegistryName = core.getCoreMetricManager().getRegistryName();
- String newRegistryName = SolrCoreMetricManager.createRegistryName(core.getCoreDescriptor().getCollectionName(), toName);
+ String newRegistryName = SolrCoreMetricManager.createRegistryName(core, toName);
metricManager.swapRegistries(oldRegistryName, newRegistryName);
registerCore(toName, core, true, false);
SolrCore old = solrCores.remove(name);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java b/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
index b2a5c79..8bfa662 100644
--- a/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
+++ b/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
@@ -20,6 +20,7 @@ import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
@@ -53,7 +54,6 @@ import org.apache.lucene.store.AlreadyClosedException;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrConfig.JmxConfiguration;
-import org.apache.solr.metrics.SolrCoreMetricManager;
import org.apache.solr.metrics.reporters.JmxObjectNameFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,9 +93,10 @@ public class JmxMonitoredMap<K, V> extends
private final String registryName;
- public JmxMonitoredMap(String coreName, String coreHashCode,
+ public JmxMonitoredMap(String coreName, String coreHashCode, String registryName,
final JmxConfiguration jmxConfig) {
this.coreHashCode = coreHashCode;
+ this.registryName = registryName;
jmxRootName = (null != jmxConfig.rootName ?
jmxConfig.rootName
: ("solr" + (null != coreName ? "/" + coreName : "")));
@@ -117,7 +118,6 @@ public class JmxMonitoredMap<K, V> extends
if (servers == null || servers.isEmpty()) {
server = null;
- registryName = null;
nameFactory = null;
log.debug("No JMX servers found, not exposing Solr information with JMX.");
return;
@@ -141,7 +141,6 @@ public class JmxMonitoredMap<K, V> extends
}
server = newServer;
}
- registryName = SolrCoreMetricManager.createRegistryName(null, coreName);
nameFactory = new JmxObjectNameFactory(REPORTER_NAME + coreHashCode, registryName);
}
@@ -166,6 +165,8 @@ public class JmxMonitoredMap<K, V> extends
for (ObjectName name : objectNames) {
try {
server.unregisterMBean(name);
+ } catch (InstanceNotFoundException ie) {
+ // ignore - someone else already deleted this one
} catch (Exception e) {
log.warn("Exception un-registering mbean {}", name, e);
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/core/SolrCore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index f22c472..13c3bdd 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -860,6 +860,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
this.configSetProperties = configSetProperties;
// Initialize the metrics manager
this.coreMetricManager = initCoreMetricManager(config);
+ this.coreMetricManager.loadReporters();
if (updateHandler == null) {
directoryFactory = initDirectoryFactory();
@@ -1101,13 +1102,12 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
*/
private SolrCoreMetricManager initCoreMetricManager(SolrConfig config) {
SolrCoreMetricManager coreMetricManager = new SolrCoreMetricManager(this);
- coreMetricManager.loadReporters();
return coreMetricManager;
}
private Map<String,SolrInfoMBean> initInfoRegistry(String name, SolrConfig config) {
if (config.jmxConfig.enabled) {
- return new JmxMonitoredMap<String, SolrInfoMBean>(name, String.valueOf(this.hashCode()), config.jmxConfig);
+ return new JmxMonitoredMap<String, SolrInfoMBean>(name, coreMetricManager.getRegistryName(), String.valueOf(this.hashCode()), config.jmxConfig);
} else {
log.debug("JMX monitoring not detected for core: " + name);
return new ConcurrentHashMap<>();
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java b/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
index bf77db4..63bdef0 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
@@ -36,9 +36,9 @@ public interface SolrInfoMBean {
SEARCHER, REPLICATION, TLOG, INDEX, DIRECTORY, HTTP, OTHER }
/**
- * Top-level group of beans for a subsystem.
+ * Top-level group of beans or metrics for a subsystem.
*/
- enum Group { jvm, jetty, node, core }
+ enum Group { jvm, jetty, node, core, collection, shard, cluster, overseer }
/**
* Simple common usage name, e.g. BasicQueryHandler,
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index e41cd8d..951d8d5 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -451,7 +451,8 @@ public class SolrXmlConfig {
return new PluginInfo[0];
PluginInfo[] configs = new PluginInfo[nodes.getLength()];
for (int i = 0; i < nodes.getLength(); i++) {
- configs[i] = new PluginInfo(nodes.item(i), "SolrMetricReporter", true, true);
+ // we don't require class in order to support predefined replica and node reporter classes
+ configs[i] = new PluginInfo(nodes.item(i), "SolrMetricReporter", true, false);
}
return configs;
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
new file mode 100644
index 0000000..de39a61
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
@@ -0,0 +1,228 @@
+/*
+ * 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.handler.admin;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.loader.ContentStreamLoader;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.handler.loader.CSVLoader;
+import org.apache.solr.handler.loader.JavabinLoader;
+import org.apache.solr.handler.loader.JsonLoader;
+import org.apache.solr.handler.loader.XMLLoader;
+import org.apache.solr.metrics.AggregateMetric;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.reporters.solr.SolrReporter;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.update.AddUpdateCommand;
+import org.apache.solr.update.CommitUpdateCommand;
+import org.apache.solr.update.DeleteUpdateCommand;
+import org.apache.solr.update.MergeIndexesCommand;
+import org.apache.solr.update.RollbackUpdateCommand;
+import org.apache.solr.update.processor.UpdateRequestProcessor;
+import org.apache.solr.util.stats.MetricUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handler to collect and aggregate metric reports. Each report indicates the target registry where
+ * metrics values should be collected and aggregated. Metrics with the same names are
+ * aggregated using {@link AggregateMetric} instances, which track the source of updates and
+ * their count, as well as providing simple statistics over collected values.
+ *
+ * Each report consists of {@link SolrInputDocument}-s that are expected to contain
+ * the following fields:
+ * <ul>
+ * <li>{@link SolrReporter#GROUP_ID} - (required) specifies target registry name where metrics will be grouped.</li>
+ * <li>{@link SolrReporter#REPORTER_ID} - (required) id of the reporter that sent this update. This can be eg.
+ * node name or replica name or other id that uniquely identifies the source of metrics values.</li>
+ * <li>{@link MetricUtils#METRIC_NAME} - (required) metric name (in the source registry)</li>
+ * <li>{@link SolrReporter#LABEL_ID} - (optional) label to prepend to metric names in the target registry.</li>
+ * <li>{@link SolrReporter#REGISTRY_ID} - (optional) name of the source registry.</li>
+ * </ul>
+ * Remaining fields are assumed to be single-valued, and to contain metric attributes and their values. Example:
+ * <pre>
+ * <doc>
+ * <field name="_group_">solr.core.collection1.shard1.leader</field>
+ * <field name="_reporter_">core_node3</field>
+ * <field name="metric">INDEX.merge.errors</field>
+ * <field name="value">0</field>
+ * </doc>
+ * </pre>
+ */
+public class MetricsCollectorHandler extends RequestHandlerBase {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String HANDLER_PATH = "/admin/metrics/collector";
+
+ private final CoreContainer coreContainer;
+ private final SolrMetricManager metricManager;
+ private final Map<String, ContentStreamLoader> loaders = new HashMap<>();
+ private SolrParams params;
+
+ public MetricsCollectorHandler(final CoreContainer coreContainer) {
+ this.coreContainer = coreContainer;
+ this.metricManager = coreContainer.getMetricManager();
+
+ }
+
+ @Override
+ public void init(NamedList initArgs) {
+ super.init(initArgs);
+ if (initArgs != null) {
+ params = SolrParams.toSolrParams(initArgs);
+ } else {
+ params = new ModifiableSolrParams();
+ }
+ loaders.put("application/xml", new XMLLoader().init(params) );
+ loaders.put("application/json", new JsonLoader().init(params) );
+ loaders.put("application/csv", new CSVLoader().init(params) );
+ loaders.put("application/javabin", new JavabinLoader().init(params) );
+ loaders.put("text/csv", loaders.get("application/csv") );
+ loaders.put("text/xml", loaders.get("application/xml") );
+ loaders.put("text/json", loaders.get("application/json"));
+ }
+
+ @Override
+ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+ if (coreContainer == null || coreContainer.isShutDown()) {
+ // silently drop request
+ return;
+ }
+ //log.info("#### " + req.toString());
+ if (req.getContentStreams() == null) { // no content
+ return;
+ }
+ for (ContentStream cs : req.getContentStreams()) {
+ if (cs.getContentType() == null) {
+ log.warn("Missing content type - ignoring");
+ continue;
+ }
+ ContentStreamLoader loader = loaders.get(cs.getContentType());
+ if (loader == null) {
+ throw new SolrException(SolrException.ErrorCode.UNSUPPORTED_MEDIA_TYPE, "Unsupported content type for stream: " + cs.getSourceInfo() + ", contentType=" + cs.getContentType());
+ }
+ loader.load(req, rsp, cs, new MetricUpdateProcessor(metricManager));
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Handler for collecting and aggregating metric reports.";
+ }
+
+ private static class MetricUpdateProcessor extends UpdateRequestProcessor {
+ private final SolrMetricManager metricManager;
+
+ public MetricUpdateProcessor(SolrMetricManager metricManager) {
+ super(null);
+ this.metricManager = metricManager;
+ }
+
+ @Override
+ public void processAdd(AddUpdateCommand cmd) throws IOException {
+ SolrInputDocument doc = cmd.solrDoc;
+ if (doc == null) {
+ return;
+ }
+ String metricName = (String)doc.getFieldValue(MetricUtils.METRIC_NAME);
+ if (metricName == null) {
+ log.warn("Missing " + MetricUtils.METRIC_NAME + " field in document, skipping: " + doc);
+ return;
+ }
+ doc.remove(MetricUtils.METRIC_NAME);
+ // XXX we could modify keys by using this original registry name
+ doc.remove(SolrReporter.REGISTRY_ID);
+ String groupId = (String)doc.getFieldValue(SolrReporter.GROUP_ID);
+ if (groupId == null) {
+ log.warn("Missing " + SolrReporter.GROUP_ID + " field in document, skipping: " + doc);
+ return;
+ }
+ doc.remove(SolrReporter.GROUP_ID);
+ String reporterId = (String)doc.getFieldValue(SolrReporter.REPORTER_ID);
+ if (reporterId == null) {
+ log.warn("Missing " + SolrReporter.REPORTER_ID + " field in document, skipping: " + doc);
+ return;
+ }
+ doc.remove(SolrReporter.REPORTER_ID);
+ String labelId = (String)doc.getFieldValue(SolrReporter.LABEL_ID);
+ doc.remove(SolrReporter.LABEL_ID);
+ doc.forEach(f -> {
+ String key = MetricRegistry.name(labelId, metricName, f.getName());
+ MetricRegistry registry = metricManager.registry(groupId);
+ AggregateMetric metric = getOrRegister(registry, key, new AggregateMetric());
+ Object o = f.getFirstValue();
+ if (o != null) {
+ metric.set(reporterId, o);
+ } else {
+ // remove missing values
+ metric.clear(reporterId);
+ }
+ });
+ }
+
+ private AggregateMetric getOrRegister(MetricRegistry registry, String name, AggregateMetric add) {
+ AggregateMetric existing = (AggregateMetric)registry.getMetrics().get(name);
+ if (existing != null) {
+ return existing;
+ }
+ try {
+ registry.register(name, add);
+ return add;
+ } catch (IllegalArgumentException e) {
+ // someone added before us
+ existing = (AggregateMetric)registry.getMetrics().get(name);
+ if (existing == null) { // now, that is weird...
+ throw new IllegalArgumentException("Inconsistent metric status, " + name);
+ }
+ return existing;
+ }
+ }
+
+ @Override
+ public void processDelete(DeleteUpdateCommand cmd) throws IOException {
+ throw new UnsupportedOperationException("processDelete");
+ }
+
+ @Override
+ public void processMergeIndexes(MergeIndexesCommand cmd) throws IOException {
+ throw new UnsupportedOperationException("processMergeIndexes");
+ }
+
+ @Override
+ public void processCommit(CommitUpdateCommand cmd) throws IOException {
+ throw new UnsupportedOperationException("processCommit");
+ }
+
+ @Override
+ public void processRollback(RollbackUpdateCommand cmd) throws IOException {
+ throw new UnsupportedOperationException("processRollback");
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
index 385317b..b53c818 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -79,7 +79,7 @@ public class MetricsHandler extends RequestHandlerBase implements PermissionName
NamedList response = new NamedList();
for (String registryName : requestedRegistries) {
MetricRegistry registry = metricManager.registry(registryName);
- response.add(registryName, MetricUtils.toNamedList(registry, metricFilters, mustMatchFilter));
+ response.add(registryName, MetricUtils.toNamedList(registry, metricFilters, mustMatchFilter, false, false, null));
}
rsp.getValues().add("metrics", response);
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/metrics/AggregateMetric.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/AggregateMetric.java b/solr/core/src/java/org/apache/solr/metrics/AggregateMetric.java
new file mode 100644
index 0000000..babc99d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/AggregateMetric.java
@@ -0,0 +1,200 @@
+/*
+ * 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.metrics;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.codahale.metrics.Metric;
+
+/**
+ * This class is used for keeping several partial named values and providing useful statistics over them.
+ */
+public class AggregateMetric implements Metric {
+
+ /**
+ * Simple class to represent current value and how many times it was set.
+ */
+ public static class Update {
+ public Object value;
+ public final AtomicInteger updateCount = new AtomicInteger();
+
+ public Update(Object value) {
+ update(value);
+ }
+
+ public void update(Object value) {
+ this.value = value;
+ updateCount.incrementAndGet();
+ }
+
+ @Override
+ public String toString() {
+ return "Update{" +
+ "value=" + value +
+ ", updateCount=" + updateCount +
+ '}';
+ }
+ }
+
+ private final Map<String, Update> values = new ConcurrentHashMap<>();
+
+ public void set(String name, Object value) {
+ final Update existing = values.get(name);
+ if (existing == null) {
+ final Update created = new Update(value);
+ final Update raced = values.putIfAbsent(name, created);
+ if (raced != null) {
+ raced.update(value);
+ }
+ } else {
+ existing.update(value);
+ }
+ }
+
+ public void clear(String name) {
+ values.remove(name);
+ }
+
+ public void clear() {
+ values.clear();
+ }
+
+ public int size() {
+ return values.size();
+ }
+
+ public boolean isEmpty() {
+ return values.isEmpty();
+ }
+
+ public Map<String, Update> getValues() {
+ return Collections.unmodifiableMap(values);
+ }
+
+ // --------- stats ---------
+ public double getMax() {
+ if (values.isEmpty()) {
+ return 0;
+ }
+ Double res = null;
+ for (Update u : values.values()) {
+ if (!(u.value instanceof Number)) {
+ continue;
+ }
+ Number n = (Number)u.value;
+ if (res == null) {
+ res = n.doubleValue();
+ continue;
+ }
+ if (n.doubleValue() > res) {
+ res = n.doubleValue();
+ }
+ }
+ return res;
+ }
+
+ public double getMin() {
+ if (values.isEmpty()) {
+ return 0;
+ }
+ Double res = null;
+ for (Update u : values.values()) {
+ if (!(u.value instanceof Number)) {
+ continue;
+ }
+ Number n = (Number)u.value;
+ if (res == null) {
+ res = n.doubleValue();
+ continue;
+ }
+ if (n.doubleValue() < res) {
+ res = n.doubleValue();
+ }
+ }
+ return res;
+ }
+
+ public double getMean() {
+ if (values.isEmpty()) {
+ return 0;
+ }
+ double total = 0;
+ for (Update u : values.values()) {
+ if (!(u.value instanceof Number)) {
+ continue;
+ }
+ Number n = (Number)u.value;
+ total += n.doubleValue();
+ }
+ return total / values.size();
+ }
+
+ public double getStdDev() {
+ int size = values.size();
+ if (size < 2) {
+ return 0;
+ }
+ final double mean = getMean();
+ double sum = 0;
+ int count = 0;
+ for (Update u : values.values()) {
+ if (!(u.value instanceof Number)) {
+ continue;
+ }
+ count++;
+ Number n = (Number)u.value;
+ final double diff = n.doubleValue() - mean;
+ sum += diff * diff;
+ }
+ if (count < 2) {
+ return 0;
+ }
+ final double variance = sum / (count - 1);
+ return Math.sqrt(variance);
+ }
+
+ public double getSum() {
+ if (values.isEmpty()) {
+ return 0;
+ }
+ double res = 0;
+ for (Update u : values.values()) {
+ if (!(u.value instanceof Number)) {
+ continue;
+ }
+ Number n = (Number)u.value;
+ res += n.doubleValue();
+ }
+ return res;
+ }
+
+ @Override
+ public String toString() {
+ return "AggregateMetric{" +
+ "size=" + size() +
+ ", max=" + getMax() +
+ ", min=" + getMin() +
+ ", mean=" + getMean() +
+ ", stddev=" + getStdDev() +
+ ", sum=" + getSum() +
+ ", values=" + values +
+ '}';
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index eb5b687..43f3535 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -20,6 +20,7 @@ import java.io.Closeable;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
+import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
@@ -36,8 +37,14 @@ public class SolrCoreMetricManager implements Closeable {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final SolrCore core;
+ private final String tag;
private final SolrMetricManager metricManager;
private String registryName;
+ private String collectionName;
+ private String shardName;
+ private String replicaName;
+ private String leaderRegistryName;
+ private boolean cloudMode;
/**
* Constructs a metric manager.
@@ -46,8 +53,26 @@ public class SolrCoreMetricManager implements Closeable {
*/
public SolrCoreMetricManager(SolrCore core) {
this.core = core;
+ this.tag = String.valueOf(core.hashCode());
this.metricManager = core.getCoreDescriptor().getCoreContainer().getMetricManager();
- registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
+ initCloudMode();
+ registryName = createRegistryName(cloudMode, collectionName, shardName, replicaName, core.getName());
+ leaderRegistryName = createLeaderRegistryName(cloudMode, collectionName, shardName);
+ }
+
+ private void initCloudMode() {
+ CloudDescriptor cd = core.getCoreDescriptor().getCloudDescriptor();
+ if (cd != null) {
+ cloudMode = true;
+ collectionName = core.getCoreDescriptor().getCollectionName();
+ shardName = cd.getShardId();
+ //replicaName = cd.getCoreNodeName();
+ String coreName = core.getName();
+ replicaName = parseReplicaName(collectionName, coreName);
+ if (replicaName == null) {
+ replicaName = cd.getCoreNodeName();
+ }
+ }
}
/**
@@ -57,7 +82,11 @@ public class SolrCoreMetricManager implements Closeable {
public void loadReporters() {
NodeConfig nodeConfig = core.getCoreDescriptor().getCoreContainer().getConfig();
PluginInfo[] pluginInfos = nodeConfig.getMetricReporterPlugins();
- metricManager.loadReporters(pluginInfos, core.getResourceLoader(), SolrInfoMBean.Group.core, registryName);
+ metricManager.loadReporters(pluginInfos, core.getResourceLoader(), tag,
+ SolrInfoMBean.Group.core, registryName);
+ if (cloudMode) {
+ metricManager.loadShardReporters(pluginInfos, core);
+ }
}
/**
@@ -67,12 +96,18 @@ public class SolrCoreMetricManager implements Closeable {
*/
public void afterCoreSetName() {
String oldRegistryName = registryName;
- registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
+ String oldLeaderRegistryName = leaderRegistryName;
+ initCloudMode();
+ registryName = createRegistryName(cloudMode, collectionName, shardName, replicaName, core.getName());
+ leaderRegistryName = createLeaderRegistryName(cloudMode, collectionName, shardName);
if (oldRegistryName.equals(registryName)) {
return;
}
// close old reporters
- metricManager.closeReporters(oldRegistryName);
+ metricManager.closeReporters(oldRegistryName, tag);
+ if (oldLeaderRegistryName != null) {
+ metricManager.closeReporters(oldLeaderRegistryName, tag);
+ }
// load reporters again, using the new core name
loadReporters();
}
@@ -96,7 +131,7 @@ public class SolrCoreMetricManager implements Closeable {
*/
@Override
public void close() throws IOException {
- metricManager.closeReporters(getRegistryName());
+ metricManager.closeReporters(getRegistryName(), tag);
}
public SolrCore getCore() {
@@ -104,7 +139,7 @@ public class SolrCoreMetricManager implements Closeable {
}
/**
- * Retrieves the metric registry name of the manager.
+ * Metric registry name of the manager.
*
* In order to make it easier for reporting tools to aggregate metrics from
* different cores that logically belong to a single collection we convert the
@@ -124,22 +159,74 @@ public class SolrCoreMetricManager implements Closeable {
return registryName;
}
- public static String createRegistryName(String collectionName, String coreName) {
- if (collectionName == null || (collectionName != null && !coreName.startsWith(collectionName + "_"))) {
- // single core, or unknown naming scheme
+ /**
+ * Metric registry name for leader metrics. This is null if not in cloud mode.
+ * @return metric registry name for leader metrics
+ */
+ public String getLeaderRegistryName() {
+ return leaderRegistryName;
+ }
+
+ /**
+ * Return a tag specific to this instance.
+ */
+ public String getTag() {
+ return tag;
+ }
+
+ public static String createRegistryName(boolean cloud, String collectionName, String shardName, String replicaName, String coreName) {
+ if (cloud) { // build registry name from logical names
+ return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, collectionName, shardName, replicaName);
+ } else {
return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, coreName);
}
- // split "collection1_shard1_1_replica1" into parts
- String str = coreName.substring(collectionName.length() + 1);
- String shard;
- String replica = null;
- int pos = str.lastIndexOf("_replica");
- if (pos == -1) { // ?? no _replicaN part ??
- shard = str;
+ }
+
+ /**
+ * This method is used by {@link org.apache.solr.core.CoreContainer#rename(String, String)}.
+ * @param aCore existing core with old name
+ * @param coreName new name
+ * @return new registry name
+ */
+ public static String createRegistryName(SolrCore aCore, String coreName) {
+ CloudDescriptor cd = aCore.getCoreDescriptor().getCloudDescriptor();
+ String replicaName = null;
+ if (cd != null) {
+ replicaName = parseReplicaName(cd.getCollectionName(), coreName);
+ }
+ return createRegistryName(
+ cd != null,
+ cd != null ? cd.getCollectionName() : null,
+ cd != null ? cd.getShardId() : null,
+ replicaName,
+ coreName
+ );
+ }
+
+ public static String parseReplicaName(String collectionName, String coreName) {
+ if (collectionName == null || !coreName.startsWith(collectionName)) {
+ return null;
+ } else {
+ // split "collection1_shard1_1_replica1" into parts
+ if (coreName.length() > collectionName.length()) {
+ String str = coreName.substring(collectionName.length() + 1);
+ int pos = str.lastIndexOf("_replica");
+ if (pos == -1) { // ?? no _replicaN part ??
+ return str;
+ } else {
+ return str.substring(pos + 1);
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static String createLeaderRegistryName(boolean cloud, String collectionName, String shardName) {
+ if (cloud) {
+ return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.collection, collectionName, shardName, "leader");
} else {
- shard = str.substring(0, pos);
- replica = str.substring(pos + 1);
+ return null;
}
- return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, collectionName, shard, replica);
}
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index cac5389..3a4c3fe 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -18,9 +18,13 @@ package org.apache.solr.metrics;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -29,6 +33,9 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Histogram;
@@ -39,9 +46,14 @@ import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.metrics.reporters.solr.SolrClusterReporter;
+import org.apache.solr.metrics.reporters.solr.SolrShardReporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -87,27 +99,39 @@ public class SolrMetricManager {
private final Lock reportersLock = new ReentrantLock();
private final Lock swapLock = new ReentrantLock();
+ public static final int DEFAULT_CLOUD_REPORTER_PERIOD = 60;
+
public SolrMetricManager() { }
/**
* An implementation of {@link MetricFilter} that selects metrics
- * with names that start with a prefix.
+ * with names that start with one of prefixes.
*/
public static class PrefixFilter implements MetricFilter {
- private final String[] prefixes;
+ private final Set<String> prefixes = new HashSet<>();
private final Set<String> matched = new HashSet<>();
private boolean allMatch = false;
/**
- * Create a filter that uses the provided prefix.
+ * Create a filter that uses the provided prefixes.
* @param prefixes prefixes to use, must not be null. If empty then any
* name will match, if not empty then match on any prefix will
* succeed (logical OR).
*/
public PrefixFilter(String... prefixes) {
Objects.requireNonNull(prefixes);
- this.prefixes = prefixes;
- if (prefixes.length == 0) {
+ if (prefixes.length > 0) {
+ this.prefixes.addAll(Arrays.asList(prefixes));
+ }
+ if (this.prefixes.isEmpty()) {
+ allMatch = true;
+ }
+ }
+
+ public PrefixFilter(Collection<String> prefixes) {
+ Objects.requireNonNull(prefixes);
+ this.prefixes.addAll(prefixes);
+ if (this.prefixes.isEmpty()) {
allMatch = true;
}
}
@@ -141,6 +165,85 @@ public class SolrMetricManager {
public void reset() {
matched.clear();
}
+
+ @Override
+ public String toString() {
+ return "PrefixFilter{" +
+ "prefixes=" + prefixes +
+ '}';
+ }
+ }
+
+ /**
+ * An implementation of {@link MetricFilter} that selects metrics
+ * with names that match regular expression patterns.
+ */
+ public static class RegexFilter implements MetricFilter {
+ private final Set<Pattern> compiledPatterns = new HashSet<>();
+ private final Set<String> matched = new HashSet<>();
+ private boolean allMatch = false;
+
+ /**
+ * Create a filter that uses the provided prefix.
+ * @param patterns regex patterns to use, must not be null. If empty then any
+ * name will match, if not empty then match on any pattern will
+ * succeed (logical OR).
+ */
+ public RegexFilter(String... patterns) throws PatternSyntaxException {
+ this(patterns != null ? Arrays.asList(patterns) : Collections.emptyList());
+ }
+
+ public RegexFilter(Collection<String> patterns) throws PatternSyntaxException {
+ Objects.requireNonNull(patterns);
+ if (patterns.isEmpty()) {
+ allMatch = true;
+ return;
+ }
+ patterns.forEach(p -> {
+ Pattern pattern = Pattern.compile(p);
+ compiledPatterns.add(pattern);
+ });
+ if (patterns.isEmpty()) {
+ allMatch = true;
+ }
+ }
+
+ @Override
+ public boolean matches(String name, Metric metric) {
+ if (allMatch) {
+ matched.add(name);
+ return true;
+ }
+ for (Pattern p : compiledPatterns) {
+ if (p.matcher(name).matches()) {
+ matched.add(name);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the set of names that matched this filter.
+ * @return matching names
+ */
+ public Set<String> getMatched() {
+ return Collections.unmodifiableSet(matched);
+ }
+
+ /**
+ * Clear the set of names that matched.
+ */
+ public void reset() {
+ matched.clear();
+ }
+
+ @Override
+ public String toString() {
+ return "RegexFilter{" +
+ "compiledPatterns=" + compiledPatterns +
+ '}';
+ }
}
/**
@@ -150,7 +253,40 @@ public class SolrMetricManager {
Set<String> set = new HashSet<>();
set.addAll(registries.keySet());
set.addAll(SharedMetricRegistries.names());
- return Collections.unmodifiableSet(set);
+ return set;
+ }
+
+ /**
+ * Return set of existing registry names that match a regex pattern
+ * @param patterns regex patterns. NOTE: users need to make sure that patterns that
+ * don't start with a wildcard use the full registry name starting with
+ * {@link #REGISTRY_NAME_PREFIX}
+ * @return set of existing registry names where at least one pattern matched.
+ */
+ public Set<String> registryNames(String... patterns) throws PatternSyntaxException {
+ if (patterns == null || patterns.length == 0) {
+ return registryNames();
+ }
+ List<Pattern> compiled = new ArrayList<>();
+ for (String pattern : patterns) {
+ compiled.add(Pattern.compile(pattern));
+ }
+ return registryNames((Pattern[])compiled.toArray(new Pattern[compiled.size()]));
+ }
+
+ public Set<String> registryNames(Pattern... patterns) {
+ Set<String> allNames = registryNames();
+ if (patterns == null || patterns.length == 0) {
+ return allNames;
+ }
+ return allNames.stream().filter(s -> {
+ for (Pattern p : patterns) {
+ if (p.matcher(s).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }).collect(Collectors.toSet());
}
/**
@@ -209,7 +345,7 @@ public class SolrMetricManager {
*/
public void removeRegistry(String registry) {
// close any reporters for this registry first
- closeReporters(registry);
+ closeReporters(registry, null);
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
if (isSharedRegistry(registry)) {
@@ -490,10 +626,12 @@ public class SolrMetricManager {
* the list. If both attributes are present then only "group" attribute will be processed.
* @param pluginInfos plugin configurations
* @param loader resource loader
+ * @param tag optional tag for the reporters, to distinguish reporters logically created for different parent
+ * component instances.
* @param group selected group, not null
* @param registryNames optional child registry name elements
*/
- public void loadReporters(PluginInfo[] pluginInfos, SolrResourceLoader loader, SolrInfoMBean.Group group, String... registryNames) {
+ public void loadReporters(PluginInfo[] pluginInfos, SolrResourceLoader loader, String tag, SolrInfoMBean.Group group, String... registryNames) {
if (pluginInfos == null || pluginInfos.length == 0) {
return;
}
@@ -533,7 +671,7 @@ public class SolrMetricManager {
}
}
try {
- loadReporter(registryName, loader, info);
+ loadReporter(registryName, loader, info, tag);
} catch (Exception e) {
log.warn("Error loading metrics reporter, plugin info: " + info, e);
}
@@ -545,9 +683,12 @@ public class SolrMetricManager {
* @param registry reporter is associated with this registry
* @param loader loader to use when creating an instance of the reporter
* @param pluginInfo plugin configuration. Plugin "name" and "class" attributes are required.
+ * @param tag optional tag for the reporter, to distinguish reporters logically created for different parent
+ * component instances.
+ * @return instance of newly created and registered reporter
* @throws Exception if any argument is missing or invalid
*/
- public void loadReporter(String registry, SolrResourceLoader loader, PluginInfo pluginInfo) throws Exception {
+ public SolrMetricReporter loadReporter(String registry, SolrResourceLoader loader, PluginInfo pluginInfo, String tag) throws Exception {
if (registry == null || pluginInfo == null || pluginInfo.name == null || pluginInfo.className == null) {
throw new IllegalArgumentException("loadReporter called with missing arguments: " +
"registry=" + registry + ", loader=" + loader + ", pluginInfo=" + pluginInfo);
@@ -558,14 +699,19 @@ public class SolrMetricManager {
pluginInfo.className,
SolrMetricReporter.class,
new String[0],
- new Class[] { SolrMetricManager.class, String.class },
- new Object[] { this, registry }
+ new Class[]{SolrMetricManager.class, String.class},
+ new Object[]{this, registry}
);
try {
reporter.init(pluginInfo);
} catch (IllegalStateException e) {
throw new IllegalArgumentException("reporter init failed: " + pluginInfo, e);
}
+ registerReporter(registry, pluginInfo.name, tag, reporter);
+ return reporter;
+ }
+
+ private void registerReporter(String registry, String name, String tag, SolrMetricReporter reporter) throws Exception {
try {
if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
throw new Exception("Could not obtain lock to modify reporters registry: " + registry);
@@ -579,12 +725,15 @@ public class SolrMetricManager {
perRegistry = new HashMap<>();
reporters.put(registry, perRegistry);
}
- SolrMetricReporter oldReporter = perRegistry.get(pluginInfo.name);
+ if (tag != null && !tag.isEmpty()) {
+ name = name + "@" + tag;
+ }
+ SolrMetricReporter oldReporter = perRegistry.get(name);
if (oldReporter != null) { // close it
- log.info("Replacing existing reporter '" + pluginInfo.name + "' in registry '" + registry + "': " + oldReporter.toString());
+ log.info("Replacing existing reporter '" + name + "' in registry '" + registry + "': " + oldReporter.toString());
oldReporter.close();
}
- perRegistry.put(pluginInfo.name, reporter);
+ perRegistry.put(name, reporter);
} finally {
reportersLock.unlock();
@@ -595,9 +744,11 @@ public class SolrMetricManager {
* Close and unregister a named {@link SolrMetricReporter} for a registry.
* @param registry registry name
* @param name reporter name
+ * @param tag optional tag for the reporter, to distinguish reporters logically created for different parent
+ * component instances.
* @return true if a named reporter existed and was closed.
*/
- public boolean closeReporter(String registry, String name) {
+ public boolean closeReporter(String registry, String name, String tag) {
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
try {
@@ -614,6 +765,9 @@ public class SolrMetricManager {
if (perRegistry == null) {
return false;
}
+ if (tag != null && !tag.isEmpty()) {
+ name = name + "@" + tag;
+ }
SolrMetricReporter reporter = perRegistry.remove(name);
if (reporter == null) {
return false;
@@ -635,6 +789,17 @@ public class SolrMetricManager {
* @return names of closed reporters
*/
public Set<String> closeReporters(String registry) {
+ return closeReporters(registry, null);
+ }
+
+ /**
+ * Close and unregister all {@link SolrMetricReporter}-s for a registry.
+ * @param registry registry name
+ * @param tag optional tag for the reporter, to distinguish reporters logically created for different parent
+ * component instances.
+ * @return names of closed reporters
+ */
+ public Set<String> closeReporters(String registry, String tag) {
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
try {
@@ -646,18 +811,28 @@ public class SolrMetricManager {
log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
return Collections.emptySet();
}
- log.info("Closing metric reporters for: " + registry);
+ log.info("Closing metric reporters for registry=" + registry + ", tag=" + tag);
try {
- Map<String, SolrMetricReporter> perRegistry = reporters.remove(registry);
+ Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
if (perRegistry != null) {
- for (SolrMetricReporter reporter : perRegistry.values()) {
+ Set<String> names = new HashSet<>(perRegistry.keySet());
+ Set<String> removed = new HashSet<>();
+ names.forEach(name -> {
+ if (tag != null && !tag.isEmpty() && !name.endsWith("@" + tag)) {
+ return;
+ }
+ SolrMetricReporter reporter = perRegistry.remove(name);
try {
reporter.close();
} catch (IOException ioe) {
log.warn("Exception closing reporter " + reporter, ioe);
}
+ removed.add(name);
+ });
+ if (removed.size() == names.size()) {
+ reporters.remove(registry);
}
- return perRegistry.keySet();
+ return removed;
} else {
return Collections.emptySet();
}
@@ -695,4 +870,114 @@ public class SolrMetricManager {
reportersLock.unlock();
}
}
+
+ private List<PluginInfo> prepareCloudPlugins(PluginInfo[] pluginInfos, String group, String className,
+ Map<String, String> defaultAttributes,
+ Map<String, Object> defaultInitArgs,
+ PluginInfo defaultPlugin) {
+ List<PluginInfo> result = new ArrayList<>();
+ if (pluginInfos == null) {
+ pluginInfos = new PluginInfo[0];
+ }
+ for (PluginInfo info : pluginInfos) {
+ String groupAttr = info.attributes.get("group");
+ if (!group.equals(groupAttr)) {
+ continue;
+ }
+ info = preparePlugin(info, className, defaultAttributes, defaultInitArgs);
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ if (result.isEmpty() && defaultPlugin != null) {
+ defaultPlugin = preparePlugin(defaultPlugin, className, defaultAttributes, defaultInitArgs);
+ if (defaultPlugin != null) {
+ result.add(defaultPlugin);
+ }
+ }
+ return result;
+ }
+
+ private PluginInfo preparePlugin(PluginInfo info, String className, Map<String, String> defaultAttributes,
+ Map<String, Object> defaultInitArgs) {
+ if (info == null) {
+ return null;
+ }
+ String classNameAttr = info.attributes.get("class");
+ if (className != null) {
+ if (classNameAttr != null && !className.equals(classNameAttr)) {
+ log.warn("Conflicting class name attributes, expected " + className + " but was " + classNameAttr + ", skipping " + info);
+ return null;
+ }
+ }
+
+ Map<String, String> attrs = new HashMap<>(info.attributes);
+ defaultAttributes.forEach((k, v) -> {
+ if (!attrs.containsKey(k)) {
+ attrs.put(k, v);
+ }
+ });
+ attrs.put("class", className);
+ Map<String, Object> initArgs = new HashMap<>();
+ if (info.initArgs != null) {
+ initArgs.putAll(info.initArgs.asMap(10));
+ }
+ defaultInitArgs.forEach((k, v) -> {
+ if (!initArgs.containsKey(k)) {
+ initArgs.put(k, v);
+ }
+ });
+ return new PluginInfo(info.type, attrs, new NamedList(initArgs), null);
+ }
+
+ public void loadShardReporters(PluginInfo[] pluginInfos, SolrCore core) {
+ // don't load for non-cloud cores
+ if (core.getCoreDescriptor().getCloudDescriptor() == null) {
+ return;
+ }
+ // prepare default plugin if none present in the config
+ Map<String, String> attrs = new HashMap<>();
+ attrs.put("name", "shardDefault");
+ attrs.put("group", SolrInfoMBean.Group.shard.toString());
+ Map<String, Object> initArgs = new HashMap<>();
+ initArgs.put("period", DEFAULT_CLOUD_REPORTER_PERIOD);
+
+ String registryName = core.getCoreMetricManager().getRegistryName();
+ // collect infos and normalize
+ List<PluginInfo> infos = prepareCloudPlugins(pluginInfos, SolrInfoMBean.Group.shard.toString(), SolrShardReporter.class.getName(),
+ attrs, initArgs, null);
+ for (PluginInfo info : infos) {
+ try {
+ SolrMetricReporter reporter = loadReporter(registryName, core.getResourceLoader(), info,
+ String.valueOf(core.hashCode()));
+ ((SolrShardReporter)reporter).setCore(core);
+ } catch (Exception e) {
+ log.warn("Could not load shard reporter, pluginInfo=" + info, e);
+ }
+ }
+ }
+
+ public void loadClusterReporters(PluginInfo[] pluginInfos, CoreContainer cc) {
+ // don't load for non-cloud instances
+ if (!cc.isZooKeeperAware()) {
+ return;
+ }
+ Map<String, String> attrs = new HashMap<>();
+ attrs.put("name", "clusterDefault");
+ attrs.put("group", SolrInfoMBean.Group.cluster.toString());
+ Map<String, Object> initArgs = new HashMap<>();
+ initArgs.put("period", DEFAULT_CLOUD_REPORTER_PERIOD);
+ List<PluginInfo> infos = prepareCloudPlugins(pluginInfos, SolrInfoMBean.Group.cluster.toString(), SolrClusterReporter.class.getName(),
+ attrs, initArgs, null);
+ String registryName = getRegistryName(SolrInfoMBean.Group.cluster);
+ for (PluginInfo info : infos) {
+ try {
+ SolrMetricReporter reporter = loadReporter(registryName, cc.getResourceLoader(), info, null);
+ ((SolrClusterReporter)reporter).setCoreContainer(cc);
+ } catch (Exception e) {
+ log.warn("Could not load node reporter, pluginInfo=" + info, e);
+ }
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java b/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
index 4df5257..1f5b4f0 100644
--- a/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
@@ -41,9 +41,9 @@ public class JmxObjectNameFactory implements ObjectNameFactory {
* @param additionalProperties additional properties as key, value pairs.
*/
public JmxObjectNameFactory(String reporterName, String domain, String... additionalProperties) {
- this.reporterName = reporterName;
+ this.reporterName = reporterName.replaceAll(":", "_");
this.domain = domain;
- this.subdomains = domain.split("\\.");
+ this.subdomains = domain.replaceAll(":", "_").split("\\.");
if (additionalProperties != null && (additionalProperties.length % 2) != 0) {
throw new IllegalArgumentException("additionalProperties length must be even: " + Arrays.toString(additionalProperties));
}
@@ -83,7 +83,7 @@ public class JmxObjectNameFactory implements ObjectNameFactory {
}
sb.append(','); // separate from other properties
} else {
- sb.append(currentDomain);
+ sb.append(currentDomain.replaceAll(":", "_"));
sb.append(':');
}
} else {
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/4d7bc947/solr/core/src/java/org/apache/solr/metrics/reporters/solr/SolrClusterReporter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/solr/SolrClusterReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/solr/SolrClusterReporter.java
new file mode 100644
index 0000000..846e805
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/solr/SolrClusterReporter.java
@@ -0,0 +1,277 @@
+/*
+ * 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.metrics.reporters.solr;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.apache.http.client.HttpClient;
+import org.apache.solr.cloud.Overseer;
+import org.apache.solr.cloud.ZkController;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.handler.admin.MetricsCollectorHandler;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricReporter;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This reporter sends selected metrics from local registries to {@link Overseer}.
+ * <p>The following configuration properties are supported:</p>
+ * <ul>
+ * <li>handler - (optional str) handler path where reports are sent. Default is
+ * {@link MetricsCollectorHandler#HANDLER_PATH}.</li>
+ * <li>period - (optional int) how often reports are sent, in seconds. Default is 60. Setting this
+ * to 0 disables the reporter.</li>
+ * <li>report - (optional multiple lst) report configuration(s), see below.</li>
+ * </ul>
+ * Each report configuration consist of the following properties:
+ * <ul>
+ * <li>registry - (required str) regex pattern matching source registries (see {@link SolrMetricManager#registryNames(String...)}),
+ * may contain capture groups.</li>
+ * <li>group - (required str) target registry name where metrics will be grouped. This can be a regex pattern that
+ * contains back-references to capture groups collected by <code>registry</code> pattern</li>
+ * <li>label - (optional str) optional prefix to prepend to metric names, may contain back-references to
+ * capture groups collected by <code>registry</code> pattern</li>
+ * <li>filter - (optional multiple str) regex expression(s) matching selected metrics to be reported.</li>
+ * </ul>
+ * NOTE: this reporter uses predefined "overseer" group, and it's always created even if explicit configuration
+ * is missing. Default configuration uses report specifications from {@link #DEFAULT_REPORTS}.
+ * <p>Example configuration:</p>
+ * <pre>
+ * <reporter name="test" group="overseer">
+ * <str name="handler">/admin/metrics/collector</str>
+ * <int name="period">11</int>
+ * <lst name="report">
+ * <str name="group">overseer</str>
+ * <str name="label">jvm</str>
+ * <str name="registry">solr\.jvm</str>
+ * <str name="filter">memory\.total\..*</str>
+ * <str name="filter">memory\.heap\..*</str>
+ * <str name="filter">os\.SystemLoadAverage</str>
+ * <str name="filter">threads\.count</str>
+ * </lst>
+ * <lst name="report">
+ * <str name="group">overseer</str>
+ * <str name="label">leader.$1</str>
+ * <str name="registry">solr\.core\.(.*)\.leader</str>
+ * <str name="filter">UPDATE\./update/.*</str>
+ * </lst>
+ * </reporter>
+ * </pre>
+ *
+ */
+public class SolrClusterReporter extends SolrMetricReporter {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String CLUSTER_GROUP = SolrMetricManager.overridableRegistryName(SolrInfoMBean.Group.cluster.toString());
+
+ public static final List<SolrReporter.Report> DEFAULT_REPORTS = new ArrayList<SolrReporter.Report>() {{
+ add(new SolrReporter.Report(CLUSTER_GROUP, "jetty",
+ SolrMetricManager.overridableRegistryName(SolrInfoMBean.Group.jetty.toString()),
+ Collections.emptySet())); // all metrics
+ add(new SolrReporter.Report(CLUSTER_GROUP, "jvm",
+ SolrMetricManager.overridableRegistryName(SolrInfoMBean.Group.jvm.toString()),
+ new HashSet<String>() {{
+ add("memory\\.total\\..*");
+ add("memory\\.heap\\..*");
+ add("os\\.SystemLoadAverage");
+ add("os\\.FreePhysicalMemorySize");
+ add("os\\.FreeSwapSpaceSize");
+ add("os\\.OpenFileDescriptorCount");
+ add("threads\\.count");
+ }})); // all metrics
+ // XXX anything interesting here?
+ //add(new SolrReporter.Specification(OVERSEER_GROUP, "node", SolrMetricManager.overridableRegistryName(SolrInfoMBean.Group.node.toString()),
+ // Collections.emptySet())); // all metrics
+ add(new SolrReporter.Report(CLUSTER_GROUP, "leader.$1", "solr\\.collection\\.(.*)\\.leader",
+ new HashSet<String>(){{
+ add("UPDATE\\./update/.*");
+ add("QUERY\\./select.*");
+ add("INDEX\\..*");
+ add("TLOG\\..*");
+ }}));
+ }};
+
+ private String handler = MetricsCollectorHandler.HANDLER_PATH;
+ private int period = SolrMetricManager.DEFAULT_CLOUD_REPORTER_PERIOD;
+ private List<SolrReporter.Report> reports = new ArrayList<>();
+
+ private SolrReporter reporter;
+
+ /**
+ * Create a reporter for metrics managed in a named registry.
+ *
+ * @param metricManager metric manager
+ * @param registryName this is ignored
+ */
+ public SolrClusterReporter(SolrMetricManager metricManager, String registryName) {
+ super(metricManager, registryName);
+ }
+
+ public void setHandler(String handler) {
+ this.handler = handler;
+ }
+
+ public void setPeriod(int period) {
+ this.period = period;
+ }
+
+ public void setReport(List<Map> reportConfig) {
+ if (reportConfig == null || reportConfig.isEmpty()) {
+ return;
+ }
+ reportConfig.forEach(map -> {
+ SolrReporter.Report r = SolrReporter.Report.fromMap(map);
+ if (r != null) {
+ reports.add(r);
+ }
+ });
+ }
+
+ // for unit tests
+ int getPeriod() {
+ return period;
+ }
+
+ List<SolrReporter.Report> getReports() {
+ return reports;
+ }
+
+ @Override
+ protected void validate() throws IllegalStateException {
+ if (period < 1) {
+ log.info("Turning off node reporter, period=" + period);
+ }
+ if (reports.isEmpty()) { // set defaults
+ reports = DEFAULT_REPORTS;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (reporter != null) {
+ reporter.close();;
+ }
+ }
+
+ public void setCoreContainer(CoreContainer cc) {
+ if (reporter != null) {
+ reporter.close();;
+ }
+ // start reporter only in cloud mode
+ if (!cc.isZooKeeperAware()) {
+ log.warn("Not ZK-aware, not starting...");
+ return;
+ }
+ if (period < 1) { // don't start it
+ return;
+ }
+ HttpClient httpClient = cc.getUpdateShardHandler().getHttpClient();
+ ZkController zk = cc.getZkController();
+ String reporterId = zk.getNodeName();
+ reporter = SolrReporter.Builder.forReports(metricManager, reports)
+ .convertRatesTo(TimeUnit.SECONDS)
+ .convertDurationsTo(TimeUnit.MILLISECONDS)
+ .withHandler(handler)
+ .withReporterId(reporterId)
+ .cloudClient(false) // we want to send reports specifically to a selected leader instance
+ .skipAggregateValues(true) // we don't want to transport details of aggregates
+ .skipHistograms(true) // we don't want to transport histograms
+ .build(httpClient, new OverseerUrlSupplier(zk));
+
+ reporter.start(period, TimeUnit.SECONDS);
+ }
+
+ // TODO: fix this when there is an elegant way to retrieve URL of a node that runs Overseer leader.
+ // package visibility for unit tests
+ static class OverseerUrlSupplier implements Supplier<String> {
+ private static final long DEFAULT_INTERVAL = 30000000; // 30s
+ private ZkController zk;
+ private String lastKnownUrl = null;
+ private long lastCheckTime = 0;
+ private long interval = DEFAULT_INTERVAL;
+
+ OverseerUrlSupplier(ZkController zk) {
+ this.zk = zk;
+ }
+
+ @Override
+ public String get() {
+ if (zk == null) {
+ return null;
+ }
+ // primitive caching for lastKnownUrl
+ long now = System.nanoTime();
+ if (lastKnownUrl != null && (now - lastCheckTime) < interval) {
+ return lastKnownUrl;
+ }
+ if (!zk.isConnected()) {
+ return lastKnownUrl;
+ }
+ lastCheckTime = now;
+ SolrZkClient zkClient = zk.getZkClient();
+ ZkNodeProps props;
+ try {
+ props = ZkNodeProps.load(zkClient.getData(
+ Overseer.OVERSEER_ELECT + "/leader", null, null, true));
+ } catch (KeeperException e) {
+ log.warn("Could not obtain overseer's address, skipping.", e);
+ return lastKnownUrl;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return lastKnownUrl;
+ }
+ if (props == null) {
+ return lastKnownUrl;
+ }
+ String oid = props.getStr("id");
+ if (oid == null) {
+ return lastKnownUrl;
+ }
+ String[] ids = oid.split("-");
+ if (ids.length != 3) { // unknown format
+ log.warn("Unknown format of leader id, skipping: " + oid);
+ return lastKnownUrl;
+ }
+ // convert nodeName back to URL
+ String url = zk.getZkStateReader().getBaseUrlForNodeName(ids[1]);
+ // check that it's parseable
+ try {
+ new java.net.URL(url);
+ } catch (MalformedURLException mue) {
+ log.warn("Malformed Overseer's leader URL: url", mue);
+ return lastKnownUrl;
+ }
+ lastKnownUrl = url;
+ return url;
+ }
+ }
+
+}