Squashed commit of branch 'feature/metrics', containing:

SOLR-4735: Improve Solr metrics reporting
    SOLR-9812: Implement /admin/metrics API
    SOLR-9805: Use metrics-jvm library to instrument jvm internals
    SOLR-9788: Use instrumented jetty classes
This commit is contained in:
Andrzej Bialecki 2016-12-20 09:31:24 +01:00
parent 84bbb8f797
commit 8bbdb6248c
52 changed files with 4095 additions and 118 deletions

View File

@ -14,6 +14,7 @@
<orderEntry type="library" scope="TEST" name="JUnit" level="project" />
<orderEntry type="library" name="Solr core library" level="project" />
<orderEntry type="library" name="Solrj library" level="project" />
<orderEntry type="library" name="Solr example library" level="project" />
<orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
<orderEntry type="module" scope="TEST" module-name="solr-test-framework" />
<orderEntry type="module" module-name="lucene-core" />

View File

@ -76,6 +76,8 @@ com.sun.jersey.version = 1.9
io.dropwizard.metrics.version = 3.1.2
/io.dropwizard.metrics/metrics-core = ${io.dropwizard.metrics.version}
/io.dropwizard.metrics/metrics-healthchecks = ${io.dropwizard.metrics.version}
/io.dropwizard.metrics/metrics-jetty9 = ${io.dropwizard.metrics.version}
/io.dropwizard.metrics/metrics-jvm = ${io.dropwizard.metrics.version}
io.netty.netty-all.version = 4.0.36.Final
/io.netty/netty-all = ${io.netty.netty-all.version}

View File

@ -173,11 +173,24 @@ New Features
* SOLR-9844: FieldCache information fetched via the mbeans handler or seen via the UI now displays the total size used.
The individual cache entries in the response are now formatted better as well. (Varun Thacker)
<<<<<<< HEAD
* SOLR-9513: Generic authentication plugins (GenericHadoopAuthPlugin and ConfigurableInternodeAuthHadoopPlugin) that delegate
all functionality to Hadoop authentication framework. (Hrishikesh Gadre via Ishan Chattopadhyaya)
* SOLR-9860: Enable configuring invariantParams via HttpSolrClient.Builder (Hrishikesh Gadre, Ishan Chattopadhyaya)
* SOLR-4735: Improve metrics reporting. This uses the dropwizard metric library, adding an internal API
for registering and reporting metrics from Solr components. Several new metrics and an improved JMX
reporter have been added (Alan Woodward, Jeff Wartes, Christine Poerschke, Kelvin Wong, shalin, ab)
* SOLR-9788: Use instrumented jetty classes provided by the dropwizard metric library. (shalin)
* SOLR-9805: Use metrics-jvm library to instrument jvm internals such as GC, memory usage and others. (shalin)
* SOLR-9812: Added a new /admin/metrics API to return all metrics collected by Solr via API. API supports two
optional parameters 'group' (all,jvm,jetty,http,node,core) and 'type' (all,counter,timer,gauge,histogram) both
of which are multi-valued. Example: http://localhost:8983/solr/admin/metrics?group=jvm,jetty&type=counter
(shalin)
Optimizations
----------------------

View File

@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicLong;
import com.codahale.metrics.Timer;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.util.stats.TimerUtils;
import org.apache.solr.util.stats.MetricUtils;
public class AnalyticsStatisticsCollector {
private final AtomicLong numRequests;
@ -95,7 +95,7 @@ public class AnalyticsStatisticsCollector {
lst.add("rangeFacets", numRangeFacets.longValue());
lst.add("queryFacets", numQueryFacets.longValue());
lst.add("queriesInQueryFacets", numQueries.longValue());
TimerUtils.addMetrics(lst, requestTimes);
MetricUtils.addMetrics(lst, requestTimes);
return lst;
}
}

View File

@ -50,7 +50,6 @@
<dependency org="log4j" name="log4j" rev="${/log4j/log4j}" conf="compile"/>
<dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="compile"/>
<dependency org="org.slf4j" name="jcl-over-slf4j" rev="${/org.slf4j/jcl-over-slf4j}" conf="compile"/>
<dependency org="io.dropwizard.metrics" name="metrics-core" rev="${/io.dropwizard.metrics/metrics-core}" conf="compile" />
<dependency org="org.easymock" name="easymock" rev="${/org.easymock/easymock}" conf="test"/>
<dependency org="cglib" name="cglib-nodep" rev="${/cglib/cglib-nodep}" conf="test"/>

View File

@ -30,7 +30,7 @@ import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.util.stats.TimerUtils;
import org.apache.solr.util.stats.MetricUtils;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -100,7 +100,7 @@ public class OverseerStatusCmd implements Cmd {
lst.add("errors", errors);
}
Timer timer = entry.getValue().requestTime;
TimerUtils.addMetrics(lst, timer);
MetricUtils.addMetrics(lst, timer);
}
results.add("overseer_operations", overseerStats);
results.add("collection_operations", collectionStats);

View File

@ -33,6 +33,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import com.codahale.metrics.Gauge;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.apache.http.auth.AuthSchemeProvider;
@ -58,6 +59,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.MetricsHandler;
import org.apache.solr.handler.admin.SecurityConfHandler;
import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
import org.apache.solr.handler.admin.SecurityConfHandlerZk;
@ -65,6 +67,8 @@ import org.apache.solr.handler.admin.ZookeeperInfoHandler;
import org.apache.solr.handler.component.ShardHandlerFactory;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.security.AuthorizationPlugin;
@ -85,6 +89,7 @@ import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PAT
import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
import static org.apache.solr.common.params.CommonParams.METRICS_PATH;
import static org.apache.solr.common.params.CommonParams.ZK_PATH;
import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
@ -156,6 +161,10 @@ public class CoreContainer {
private BackupRepositoryFactory backupRepoFactory;
protected SolrMetricManager metricManager;
protected MetricsHandler metricsHandler;
/**
* This method instantiates a new instance of {@linkplain BackupRepository}.
*
@ -423,6 +432,10 @@ public class CoreContainer {
return pkiAuthenticationPlugin;
}
public SolrMetricManager getMetricManager() {
return metricManager;
}
//-------------------------------------------------------------------
// Initialization / Cleanup
//-------------------------------------------------------------------
@ -463,28 +476,45 @@ public class CoreContainer {
MDCLoggingContext.setNode(this);
metricManager = new SolrMetricManager();
securityConfHandler = isZooKeeperAware() ? new SecurityConfHandlerZk(this) : new SecurityConfHandlerLocal(this);
reloadSecurityProperties();
this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
containerHandlers.put(ZK_PATH, new ZookeeperInfoHandler(this));
collectionsHandler = createHandler(cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
infoHandler = createHandler(cfg.getInfoHandlerClass(), InfoHandler.class);
containerHandlers.put(INFO_HANDLER_PATH, infoHandler);
coreAdminHandler = createHandler(cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
containerHandlers.put(CORES_HANDLER_PATH, coreAdminHandler);
configSetsHandler = createHandler(cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class);
containerHandlers.put(CONFIGSETS_HANDLER_PATH, configSetsHandler);
createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
infoHandler = createHandler(INFO_HANDLER_PATH, cfg.getInfoHandlerClass(), InfoHandler.class);
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);
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, SolrInfoMBean.Group.http);
coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController);
containerProperties.putAll(cfg.getSolrProperties());
// initialize gauges for reporting the number of cores
Gauge<Integer> loadedCores = () -> solrCores.getCores().size();
Gauge<Integer> lazyCores = () -> solrCores.getCoreNames().size() - solrCores.getCores().size();
Gauge<Integer> unloadedCores = () -> solrCores.getAllCoreNames().size() - solrCores.getCoreNames().size();
metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
loadedCores, true, "loaded", "cores");
metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
lazyCores, true, "lazy", "cores");
metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
unloadedCores, true, "unloaded", "cores");
// setup executor to load cores in parallel
ExecutorService coreLoadExecutor = ExecutorUtil.newMDCAwareFixedThreadPool(
cfg.getCoreLoadThreadCount(isZooKeeperAware()),
@ -658,6 +688,10 @@ 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) {
@ -1034,6 +1068,9 @@ public class CoreContainer {
SolrCore core = solrCores.remove(name);
coresLocator.delete(this, cd);
// delete metrics specific to this core
metricManager.removeRegistry(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, name));
if (core == null) {
// transient core
SolrCore.deleteUnloadedCore(cd, deleteDataDir, deleteInstanceDir);
@ -1167,8 +1204,15 @@ public class CoreContainer {
// ---------------- CoreContainer request handlers --------------
protected <T> T createHandler(String handlerClass, Class<T> clazz) {
return loader.newInstance(handlerClass, clazz, null, new Class[] { CoreContainer.class }, new Object[] { this });
protected <T> T createHandler(String path, String handlerClass, Class<T> clazz) {
T handler = loader.newInstance(handlerClass, clazz, null, new Class[] { CoreContainer.class }, new Object[] { this });
if (handler instanceof SolrRequestHandler) {
containerHandlers.put(path, (SolrRequestHandler)handler);
}
if (handler instanceof SolrMetricProducer) {
((SolrMetricProducer)handler).initializeMetrics(metricManager, SolrInfoMBean.Group.node.toString(), path);
}
return handler;
}
public CoreAdminHandler getMultiCoreHandler() {

View File

@ -60,13 +60,16 @@ public class NodeConfig {
private final PluginInfo[] backupRepositoryPlugins;
private final PluginInfo[] metricReporterPlugins;
private NodeConfig(String nodeName, Path coreRootDirectory, Path configSetBaseDirectory, String sharedLibDirectory,
PluginInfo shardHandlerFactoryConfig, UpdateShardHandlerConfig updateShardHandlerConfig,
String coreAdminHandlerClass, String collectionsAdminHandlerClass,
String infoHandlerClass, String configSetsHandlerClass,
LogWatcherConfig logWatcherConfig, CloudConfig cloudConfig, Integer coreLoadThreads,
int transientCacheSize, boolean useSchemaCache, String managementPath, SolrResourceLoader loader,
Properties solrProperties, PluginInfo[] backupRepositoryPlugins) {
Properties solrProperties, PluginInfo[] backupRepositoryPlugins,
PluginInfo[] metricReporterPlugins) {
this.nodeName = nodeName;
this.coreRootDirectory = coreRootDirectory;
this.configSetBaseDirectory = configSetBaseDirectory;
@ -86,6 +89,7 @@ public class NodeConfig {
this.loader = loader;
this.solrProperties = solrProperties;
this.backupRepositoryPlugins = backupRepositoryPlugins;
this.metricReporterPlugins = metricReporterPlugins;
if (this.cloudConfig != null && this.getCoreLoadThreadCount(false) < 2) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
@ -174,6 +178,10 @@ public class NodeConfig {
return backupRepositoryPlugins;
}
public PluginInfo[] getMetricReporterPlugins() {
return metricReporterPlugins;
}
public static class NodeConfigBuilder {
private Path coreRootDirectory;
@ -193,6 +201,7 @@ public class NodeConfig {
private String managementPath;
private Properties solrProperties = new Properties();
private PluginInfo[] backupRepositoryPlugins;
private PluginInfo[] metricReporterPlugins;
private final SolrResourceLoader loader;
private final String nodeName;
@ -300,11 +309,16 @@ public class NodeConfig {
return this;
}
public NodeConfigBuilder setMetricReporterPlugins(PluginInfo[] metricReporterPlugins) {
this.metricReporterPlugins = metricReporterPlugins;
return this;
}
public NodeConfig build() {
return new NodeConfig(nodeName, coreRootDirectory, configSetBaseDirectory, sharedLibDirectory, shardHandlerFactoryConfig,
updateShardHandlerConfig, coreAdminHandlerClass, collectionsAdminHandlerClass, infoHandlerClass, configSetsHandlerClass,
logWatcherConfig, cloudConfig, coreLoadThreads, transientCacheSize, useSchemaCache, managementPath, loader, solrProperties,
backupRepositoryPlugins);
backupRepositoryPlugins, metricReporterPlugins);
}
}
}

View File

@ -111,6 +111,7 @@ public class PluginInfo implements MapSerializable {
if (type != null) sb.append("type = " + type + ",");
if (name != null) sb.append("name = " + name + ",");
if (className != null) sb.append("class = " + className + ",");
if (attributes != null && attributes.size() > 0) sb.append("attributes = " + attributes + ",");
if (initArgs != null && initArgs.size() > 0) sb.append("args = " + initArgs);
sb.append("}");
return sb.toString();
@ -181,7 +182,8 @@ public class PluginInfo implements MapSerializable {
}
public PluginInfo copy() {
PluginInfo result = new PluginInfo(type, attributes, initArgs.clone(), children);
PluginInfo result = new PluginInfo(type, attributes,
initArgs != null ? initArgs.clone() : null, children);
result.isFromSolrConfig = isFromSolrConfig;
return result;
}

View File

@ -53,6 +53,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Timer;
import com.google.common.collect.MapMaker;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.util.ResourceLoader;
@ -94,6 +96,9 @@ import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.handler.component.HighlightComponent;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.metrics.SolrCoreMetricManager;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.BinaryResponseWriter;
@ -187,6 +192,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
private final PluginBag<SearchComponent> searchComponents = new PluginBag<>(SearchComponent.class, this);
private final PluginBag<UpdateRequestProcessorFactory> updateProcessors = new PluginBag<>(UpdateRequestProcessorFactory.class, this, true);
private final Map<String,UpdateRequestProcessorChain> updateProcessorChains;
private final SolrCoreMetricManager coreMetricManager;
private final Map<String, SolrInfoMBean> infoRegistry;
private final IndexDeletionPolicyWrapper solrDelPolicy;
private final SolrSnapshotMetaDataManager snapshotMgr;
@ -200,6 +206,12 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
private final ReentrantLock ruleExpiryLock;
private final ReentrantLock snapshotDelLock; // A lock instance to guard against concurrent deletions.
private final Timer newSearcherTimer;
private final Timer newSearcherWarmupTimer;
private final Counter newSearcherCounter;
private final Counter newSearcherMaxReachedCounter;
private final Counter newSearcherOtherErrorsCounter;
public Date getStartTimeStamp() { return startTime; }
private final Map<Object, IndexFingerprint> perSegmentFingerprintCache = new MapMaker().weakKeys().makeMap();
@ -386,9 +398,13 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
}
public void setName(String v) {
String oldName = this.name;
this.name = v;
this.logid = (v==null)?"":("["+v+"] ");
this.coreDescriptor = new CoreDescriptor(v, this.coreDescriptor);
if (coreMetricManager != null) {
coreMetricManager.afterCoreSetName();
}
}
public String getLogId()
@ -396,6 +412,15 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
return this.logid;
}
/**
* Returns the {@link SolrCoreMetricManager} for this core.
*
* @return the {@link SolrCoreMetricManager} for this core
*/
public SolrCoreMetricManager getCoreMetricManager() {
return coreMetricManager;
}
/**
* Returns a Map of name vs SolrInfoMBean objects. The returned map is an instance of
* a ConcurrentHashMap and therefore no synchronization is needed for putting, removing
@ -838,6 +863,18 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
checkVersionFieldExistsInSchema(schema, coreDescriptor);
// Initialize the metrics manager
this.coreMetricManager = initCoreMetricManager(config);
SolrMetricManager metricManager = this.coreDescriptor.getCoreContainer().getMetricManager();
// initialize searcher-related metrics
newSearcherCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcher");
newSearcherTimer = metricManager.timer(coreMetricManager.getRegistryName(), "newSearcherTime");
newSearcherWarmupTimer = metricManager.timer(coreMetricManager.getRegistryName(), "newSearcherWarmup");
newSearcherMaxReachedCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcherMaxReached");
newSearcherOtherErrorsCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcherErrors");
// Initialize JMX
this.infoRegistry = initInfoRegistry(name, config);
infoRegistry.put("fieldCache", new SolrFieldCacheMBean());
@ -1041,6 +1078,19 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
setLatestSchema(schema);
}
/**
* Initializes the core's {@link SolrCoreMetricManager} with a given configuration.
* If metric reporters are configured, they are also initialized for this core.
*
* @param config the given configuration
* @return an instance of {@link SolrCoreMetricManager}
*/
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);
@ -1361,6 +1411,15 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
}
}
try {
coreMetricManager.close();
} catch (Throwable e) {
SolrException.log(log, e);
if (e instanceof Error) {
throw (Error) e;
}
}
// Close the snapshots meta-data directory.
Directory snapshotsDir = snapshotMgr.getSnapshotsDir();
try {
@ -1920,12 +1979,14 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
// first: increment count to signal other threads that we are
// opening a new searcher.
onDeckSearchers++;
newSearcherCounter.inc();
if (onDeckSearchers < 1) {
// should never happen... just a sanity check
log.error(logid + "ERROR!!! onDeckSearchers is " + onDeckSearchers);
onDeckSearchers = 1; // reset
} else if (onDeckSearchers > maxWarmingSearchers) {
onDeckSearchers--;
newSearcherMaxReachedCounter.inc();
try {
searcherLock.wait();
} catch (InterruptedException e) {
@ -1947,6 +2008,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
boolean success = false;
openSearcherLock.lock();
Timer.Context timerContext = newSearcherTimer.time();
try {
searchHolder = openNewSearcher(updateHandlerReopens, false);
// the searchHolder will be incremented once already (and it will eventually be assigned to _searcher when registered)
@ -1989,6 +2051,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
// should this go before the other event handlers or after?
if (currSearcher != null) {
future = searcherExecutor.submit(() -> {
Timer.Context warmupContext = newSearcherWarmupTimer.time();
try {
newSearcher.warm(currSearcher);
} catch (Throwable e) {
@ -1996,6 +2059,8 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
if (e instanceof Error) {
throw (Error) e;
}
} finally {
warmupContext.close();
}
return null;
});
@ -2076,7 +2141,10 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
} finally {
timerContext.close();
if (!success) {
newSearcherOtherErrorsCounter.inc();;
synchronized (searcherLock) {
onDeckSearchers--;
@ -2750,6 +2818,11 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
public void registerInfoBean(String name, SolrInfoMBean solrInfoMBean) {
infoRegistry.put(name, solrInfoMBean);
if (solrInfoMBean instanceof SolrMetricProducer) {
SolrMetricProducer producer = (SolrMetricProducer) solrInfoMBean;
coreMetricManager.registerMetricProducer(name, producer);
}
}
private static boolean checkStale(SolrZkClient zkClient, String zkPath, int currentVersion) {

View File

@ -29,7 +29,15 @@ import org.apache.solr.common.util.NamedList;
*/
public interface SolrInfoMBean {
public enum Category { CORE, QUERYHANDLER, UPDATEHANDLER, CACHE, HIGHLIGHTING, QUERYPARSER, OTHER };
/**
* Category of {@link SolrCore} component.
*/
enum Category { CORE, QUERYHANDLER, UPDATEHANDLER, CACHE, HIGHLIGHTING, QUERYPARSER, OTHER }
/**
* Top-level group of beans for a subsystem.
*/
enum Group { jvm, jetty, http, node, core }
/**
* Simple common usage name, e.g. BasicQueryHandler,

View File

@ -95,7 +95,8 @@ public class SolrXmlConfig {
configBuilder.setSolrProperties(loadProperties(config));
if (cloudConfig != null)
configBuilder.setCloudConfig(cloudConfig);
configBuilder.setBackupRepositoryPlugins((getBackupRepositoryPluginInfos(config)));
configBuilder.setBackupRepositoryPlugins(getBackupRepositoryPluginInfos(config));
configBuilder.setMetricReporterPlugins(getMetricReporterPluginInfos(config));
return fillSolrSection(configBuilder, entries);
}
@ -436,5 +437,16 @@ public class SolrXmlConfig {
}
return configs;
}
private static PluginInfo[] getMetricReporterPluginInfos(Config config) {
NodeList nodes = (NodeList) config.evaluate("solr/metrics/reporter", XPathConstants.NODESET);
if (nodes == null || nodes.getLength() == 0)
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);
}
return configs;
}
}

View File

@ -18,8 +18,11 @@ package org.apache.solr.handler;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.util.concurrent.atomic.LongAdder;
import java.util.Arrays;
import java.util.Collection;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
@ -29,12 +32,14 @@ import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.SolrPluginUtils;
import org.apache.solr.util.stats.TimerUtils;
import org.apache.solr.util.stats.MetricUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,7 +48,7 @@ import static org.apache.solr.core.RequestParams.USEPARAM;
/**
*
*/
public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, NestedRequestHandler {
public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler {
protected NamedList initArgs = null;
protected SolrParams defaults;
@ -52,11 +57,12 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
protected boolean httpCaching = true;
// Statistics
private final LongAdder numRequests = new LongAdder();
private final LongAdder numServerErrors = new LongAdder();
private final LongAdder numClientErrors = new LongAdder();
private final LongAdder numTimeouts = new LongAdder();
private final Timer requestTimes = new Timer();
private Meter numErrors = new Meter();
private Meter numServerErrors = new Meter();
private Meter numClientErrors = new Meter();
private Meter numTimeouts = new Meter();
private Counter requests = new Counter();
private Timer requestTimes = new Timer();
private final long handlerStart;
@ -126,6 +132,17 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
}
@Override
public Collection<String> initializeMetrics(SolrMetricManager manager, String registryName, String scope) {
numErrors = manager.meter(registryName, "errors", getCategory().toString(), scope);
numServerErrors = manager.meter(registryName, "serverErrors", getCategory().toString(), scope);
numClientErrors = manager.meter(registryName, "clientErrors", getCategory().toString(), scope);
numTimeouts = manager.meter(registryName, "timeouts", getCategory().toString(), scope);
requests = manager.counter(registryName, "requests", getCategory().toString(), scope);
requestTimes = manager.timer(registryName, "requestTimes", getCategory().toString(), scope);
return Arrays.asList("errors", "serverErrors", "clientErrors", "timeouts", "requestTimes", "requests");
}
public static SolrParams getSolrParamsFromNamedList(NamedList args, String key) {
Object o = args.get(key);
if (o != null && o instanceof NamedList) {
@ -142,7 +159,7 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
@Override
public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) {
numRequests.increment();
requests.inc();
Timer.Context timer = requestTimes.time();
try {
if(pluginInfo != null && pluginInfo.attributes.containsKey(USEPARAM)) req.getContext().put(USEPARAM,pluginInfo.attributes.get(USEPARAM));
@ -156,7 +173,7 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
Object partialResults = header.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY);
boolean timedOut = partialResults == null ? false : (Boolean)partialResults;
if( timedOut ) {
numTimeouts.increment();
numTimeouts.mark();
rsp.setHttpCaching(false);
}
}
@ -182,14 +199,14 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
if (incrementErrors) {
SolrException.log(log, e);
numErrors.mark();
if (isServerError) {
numServerErrors.increment();
numServerErrors.mark();
} else {
numClientErrors.increment();
numClientErrors.mark();
}
}
}
finally {
} finally {
timer.stop();
}
}
@ -268,15 +285,14 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
public NamedList<Object> getStatistics() {
NamedList<Object> lst = new SimpleOrderedMap<>();
lst.add("handlerStart",handlerStart);
lst.add("requests", numRequests.longValue());
lst.add("errors", numServerErrors.longValue() + numClientErrors.longValue());
lst.add("serverErrors", numServerErrors.longValue());
lst.add("clientErrors", numClientErrors.longValue());
lst.add("timeouts", numTimeouts.longValue());
TimerUtils.addMetrics(lst, requestTimes);
lst.add("requests", requests.getCount());
lst.add("errors", numErrors.getCount());
lst.add("serverErrors", numServerErrors.getCount());
lst.add("clientErrors", numClientErrors.getCount());
lst.add("timeouts", numTimeouts.getCount());
MetricUtils.addMetrics(lst, requestTimes);
return lst;
}
}

View File

@ -0,0 +1,164 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.handler.admin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.stats.MetricUtils;
/**
* Request handler to return metrics
*/
public class MetricsHandler extends RequestHandlerBase implements PermissionNameProvider {
final CoreContainer container;
final SolrMetricManager metricManager;
public MetricsHandler() {
this.container = null;
this.metricManager = null;
}
public MetricsHandler(CoreContainer container) {
this.container = container;
this.metricManager = this.container.getMetricManager();
}
@Override
public Name getPermissionName(AuthorizationContext request) {
return Name.METRICS_READ_PERM;
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
if (container == null) {
throw new SolrException(SolrException.ErrorCode.INVALID_STATE, "Core container instance not initialized");
}
List<MetricType> metricTypes = parseMetricTypes(req);
List<MetricFilter> metricFilters = metricTypes.stream().map(MetricType::asMetricFilter).collect(Collectors.toList());
List<Group> requestedGroups = parseGroups(req);
NamedList response = new NamedList();
for (Group group : requestedGroups) {
String registryName = SolrMetricManager.getRegistryName(group);
if (group == Group.core) {
// this requires special handling because of the way we create registry name for a core (deeply nested)
container.getAllCoreNames().forEach(s -> {
String coreRegistryName;
try (SolrCore core = container.getCore(s)) {
coreRegistryName = core.getCoreMetricManager().getRegistryName();
}
MetricRegistry registry = metricManager.registry(coreRegistryName);
response.add(coreRegistryName, MetricUtils.toNamedList(registry, metricFilters));
});
} else {
MetricRegistry registry = metricManager.registry(registryName);
response.add(registryName, MetricUtils.toNamedList(registry, metricFilters));
}
}
rsp.getValues().add("metrics", response);
}
private List<Group> parseGroups(SolrQueryRequest req) {
String[] groupStr = req.getParams().getParams("group");
List<String> groups = Collections.emptyList();
if (groupStr != null && groupStr.length > 0) {
groups = new ArrayList<>();
for (String g : groupStr) {
groups.addAll(StrUtils.splitSmart(g, ','));
}
}
List<Group> requestedGroups = Arrays.asList(Group.values()); // by default we return all groups
try {
if (groups.size() > 0 && !groups.contains("all")) {
requestedGroups = groups.stream().map(String::trim).map(Group::valueOf).collect(Collectors.toList());
}
} catch (IllegalArgumentException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid group in: " + groups + " specified. Must be one of (all, jvm, jetty, http, node, core)", e);
}
return requestedGroups;
}
private List<MetricType> parseMetricTypes(SolrQueryRequest req) {
String[] typeStr = req.getParams().getParams("type");
List<String> types = Collections.emptyList();
if (typeStr != null && typeStr.length > 0) {
types = new ArrayList<>();
for (String type : typeStr) {
types.addAll(StrUtils.splitSmart(type, ','));
}
}
List<MetricType> metricTypes = Collections.singletonList(MetricType.all); // include all metrics by default
try {
if (types.size() > 0) {
metricTypes = types.stream().map(String::trim).map(MetricType::valueOf).collect(Collectors.toList());
}
} catch (IllegalArgumentException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid metric type in: " + types + " specified. Must be one of (all, meter, timer, histogram, counter, gauge)", e);
}
return metricTypes;
}
@Override
public String getDescription() {
return "A handler to return all the metrics gathered by Solr";
}
enum MetricType {
histogram(Histogram.class),
meter(Meter.class),
timer(Timer.class),
counter(Counter.class),
gauge(Gauge.class),
all(null);
private final Class klass;
MetricType(Class klass) {
this.klass = klass;
}
public MetricFilter asMetricFilter() {
return (name, metric) -> klass == null || klass.isInstance(metric);
}
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.lang.invoke.MethodHandles;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Responsible for collecting metrics from {@link SolrMetricProducer}'s
* and exposing metrics to {@link SolrMetricReporter}'s.
*/
public class SolrCoreMetricManager implements Closeable {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final SolrCore core;
private final SolrMetricManager metricManager;
private String registryName;
/**
* Constructs a metric manager.
*
* @param core the metric manager's core
*/
public SolrCoreMetricManager(SolrCore core) {
this.core = core;
this.metricManager = core.getCoreDescriptor().getCoreContainer().getMetricManager();
registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
}
/**
* Load reporters configured globally and specific to {@link org.apache.solr.core.SolrInfoMBean.Group#core}
* group or with a registry name specific to this core.
*/
public void loadReporters() {
NodeConfig nodeConfig = core.getCoreDescriptor().getCoreContainer().getConfig();
PluginInfo[] pluginInfos = nodeConfig.getMetricReporterPlugins();
metricManager.loadReporters(pluginInfos, core.getResourceLoader(), SolrInfoMBean.Group.core, registryName);
}
/**
* Make sure that metrics already collected that correspond to the old core name
* are carried over and will be used under the new core name.
* This method also reloads reporters so that they use the new core name.
*/
public void afterCoreSetName() {
String oldRegistryName = registryName;
registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
if (oldRegistryName.equals(registryName)) {
return;
}
// close old reporters
metricManager.closeReporters(oldRegistryName);
metricManager.moveMetrics(oldRegistryName, registryName, null);
// old registry is no longer used - we have moved the metrics
metricManager.removeRegistry(oldRegistryName);
// load reporters again, using the new core name
loadReporters();
}
/**
* Registers a mapping of name/metric's with the manager's metric registry.
*
* @param scope the scope of the metrics to be registered (e.g. `/admin/ping`)
* @param producer producer of metrics to be registered
*/
public void registerMetricProducer(String scope, SolrMetricProducer producer) {
if (scope == null || producer == null || producer.getCategory() == null) {
throw new IllegalArgumentException("registerMetricProducer() called with illegal arguments: " +
"scope = " + scope + ", producer = " + producer);
}
Collection<String> registered = producer.initializeMetrics(metricManager, getRegistryName(), scope);
if (registered == null || registered.isEmpty()) {
throw new IllegalArgumentException("registerMetricProducer() did not register any metrics " +
"for scope = " + scope + ", producer = " + producer);
}
}
/**
* Closes reporters specific to this core.
*/
@Override
public void close() throws IOException {
metricManager.closeReporters(getRegistryName());
}
public SolrCore getCore() {
return core;
}
/**
* Retrieves the 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
* core name into a dot-separated hierarchy of: collection name, shard name (with optional split)
* and replica name.
*
* <p>For example, when the core name looks like this but it's NOT a SolrCloud collection:
* <code>my_collection_shard1_1_replica1</code> then this will be used as the registry name (plus
* the required <code>solr.core</code> prefix). However,
* if this is a SolrCloud collection <code>my_collection</code> then the registry name will become
* <code>solr.core.my_collection.shard1_1.replica1</code>.</p>
*
*
* @return the metric registry name of the manager.
*/
public String getRegistryName() {
return registryName;
}
/* package visibility for tests. */
String createRegistryName(String collectionName, String coreName) {
if (collectionName == null || (collectionName != null && !coreName.startsWith(collectionName + "_"))) {
// single core, or unknown naming scheme
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;
} else {
shard = str.substring(0, pos);
replica = str.substring(pos + 1);
}
return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, collectionName, shard, replica);
}
}

View File

@ -0,0 +1,104 @@
/*
* 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 com.codahale.metrics.MetricRegistry;
import org.apache.solr.core.SolrInfoMBean;
/**
* Wraps meta-data for a metric.
*/
public final class SolrMetricInfo {
public final String name;
public final String scope;
public final SolrInfoMBean.Category category;
/**
* Creates a new instance of {@link SolrMetricInfo}.
*
* @param category the category of the metric (e.g. `QUERYHANDLERS`)
* @param scope the scope of the metric (e.g. `/admin/ping`)
* @param name the name of the metric (e.g. `Requests`)
*/
public SolrMetricInfo(SolrInfoMBean.Category category, String scope, String name) {
this.name = name;
this.scope = scope;
this.category = category;
}
public static SolrMetricInfo of(String fullName) {
if (fullName == null || fullName.isEmpty()) {
return null;
}
String[] names = fullName.split("\\.");
if (names.length < 3) { // not a valid info
return null;
}
// check top-level name for valid category
SolrInfoMBean.Category category;
try {
category = SolrInfoMBean.Category.valueOf(names[0]);
} catch (IllegalArgumentException e) { // not a valid category
return null;
}
String scope = names[1];
String name = fullName.substring(names[0].length() + names[1].length() + 2);
return new SolrMetricInfo(category, scope, name);
}
/**
* Returns the metric name defined by this object.
* For example, if the name is `Requests`, scope is `/admin/ping`,
* and category is `QUERYHANDLERS`, then the metric name is
* `QUERYHANDLERS./admin/ping.Requests`.
*
* @return the metric name defined by this object
*/
public String getMetricName() {
return MetricRegistry.name(category.toString(), scope, name);
}
@Override
public String toString() {
return "SolrMetricInfo{" +
"name='" + name + '\'' +
", scope='" + scope + '\'' +
", category=" + category +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SolrMetricInfo that = (SolrMetricInfo) o;
if (name != null ? !name.equals(that.name) : that.name != null) return false;
if (scope != null ? !scope.equals(that.scope) : that.scope != null) return false;
return category == that.category;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (scope != null ? scope.hashCode() : 0);
result = 31 * result + (category != null ? category.hashCode() : 0);
return result;
}
}

View File

@ -0,0 +1,652 @@
/*
* 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.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.SharedMetricRegistries;
import com.codahale.metrics.Timer;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.core.SolrResourceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class maintains a repository of named {@link MetricRegistry} instances, and provides several
* helper methods for managing various aspects of metrics reporting:
* <ul>
* <li>registry creation, clearing and removal,</li>
* <li>creation of most common metric implementations,</li>
* <li>management of {@link SolrMetricReporter}-s specific to a named registry.</li>
* </ul>
* {@link MetricRegistry} instances are automatically created when first referenced by name. Similarly,
* instances of {@link Metric} implementations, such as {@link Meter}, {@link Counter}, {@link Timer} and
* {@link Histogram} are automatically created and registered under hierarchical names, in a specified
* registry, when {@link #meter(String, String, String...)} and other similar methods are called.
* <p>This class enforces a common prefix ({@link #REGISTRY_NAME_PREFIX}) in all registry
* names.</p>
* <p>Solr uses several different registries for collecting metrics belonging to different groups, using
* {@link org.apache.solr.core.SolrInfoMBean.Group} as the main name of the registry (plus the
* above-mentioned prefix). Instances of {@link SolrMetricManager} are created for each {@link org.apache.solr.core.CoreContainer},
* and most registries are local to each instance, with the exception of two global registries:
* <code>solr.jetty</code> and <code>solr.jvm</code>, which are shared between all {@link org.apache.solr.core.CoreContainer}-s</p>
*/
public class SolrMetricManager {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public static final String REGISTRY_NAME_PREFIX = "solr.";
public static final String JETTY_REGISTRY = REGISTRY_NAME_PREFIX + SolrInfoMBean.Group.jetty.toString();
public static final String JVM_REGISTRY = REGISTRY_NAME_PREFIX + SolrInfoMBean.Group.jvm.toString();
private final ConcurrentMap<String, MetricRegistry> registries = new ConcurrentHashMap<>();
// these reporters are per CoreContainer
private final Map<String, Map<String, SolrMetricReporter>> reporters = new HashMap<>();
// these reporters are per JVM
private static final Map<String, Map<String, SolrMetricReporter>> sharedReporters = new HashMap<>();
private final Lock reportersLock = new ReentrantLock();
public SolrMetricManager() { }
/**
* An implementation of {@link MetricFilter} that selects metrics
* with names that start with a prefix.
*/
public static class PrefixFilter implements MetricFilter {
private final String prefix;
private final Set<String> matched = new HashSet<>();
/**
* Create a filter that uses the provided prefix.
* @param prefix prefix to use, must not be null. If empty then any
* name will match.
*/
public PrefixFilter(String prefix) {
Objects.requireNonNull(prefix);
this.prefix = prefix;
}
@Override
public boolean matches(String name, Metric metric) {
if (prefix.isEmpty()) {
matched.add(name);
return true;
}
if (name.startsWith(prefix)) {
matched.add(name);
return true;
} else {
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();
}
}
/**
* Return a set of existing registry names.
*/
public Set<String> registryNames() {
Set<String> set = new HashSet<>();
set.addAll(registries.keySet());
set.addAll(SharedMetricRegistries.names());
return Collections.unmodifiableSet(set);
}
/**
* Get (or create if not present) a named registry
* @param registry name of the registry
* @return existing or newly created registry
*/
public MetricRegistry registry(String registry) {
registry = overridableRegistryName(registry);
if (JETTY_REGISTRY.equals(registry) || JVM_REGISTRY.equals(registry)) {
return SharedMetricRegistries.getOrCreate(registry);
} else {
final MetricRegistry existing = registries.get(registry);
if (existing == null) {
final MetricRegistry created = new MetricRegistry();
final MetricRegistry raced = registries.putIfAbsent(registry, created);
if (raced == null) {
return created;
} else {
return raced;
}
} else {
return existing;
}
}
}
/**
* Remove a named registry.
* @param registry name of the registry to remove
*/
public void removeRegistry(String registry) {
// close any reporters for this registry first
closeReporters(registry);
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
if (JETTY_REGISTRY.equals(registry) || JVM_REGISTRY.equals(registry)) {
SharedMetricRegistries.remove(registry);
} else {
registries.remove(registry);
}
}
/**
* Move all matching metrics from one registry to another. This is useful eg. during
* {@link org.apache.solr.core.SolrCore} rename or swap operations.
* @param fromRegistry source registry
* @param toRegistry target registry
* @param filter optional {@link MetricFilter} to select what metrics to move. If null
* then all metrics will be moved.
*/
public void moveMetrics(String fromRegistry, String toRegistry, MetricFilter filter) {
MetricRegistry from = registry(fromRegistry);
MetricRegistry to = registry(toRegistry);
if (from == to) {
return;
}
if (filter == null) {
to.registerAll(from);
from.removeMatching(MetricFilter.ALL);
} else {
for (Map.Entry<String, Metric> entry : from.getMetrics().entrySet()) {
if (filter.matches(entry.getKey(), entry.getValue())) {
to.register(entry.getKey(), entry.getValue());
}
}
from.removeMatching(filter);
}
}
/**
* Register all metrics in the provided {@link MetricSet}, optionally skipping those that
* already exist.
* @param registry registry name
* @param metrics metric set to register
* @param force if true then already existing metrics with the same name will be replaced.
* When false and a metric with the same name already exists an exception
* will be thrown.
* @param metricPath (optional) additional top-most metric name path elements
* @throws Exception if a metric with this name already exists.
*/
public void registerAll(String registry, MetricSet metrics, boolean force, String... metricPath) throws Exception {
MetricRegistry metricRegistry = registry(registry);
synchronized (metricRegistry) {
Map<String, Metric> existingMetrics = metricRegistry.getMetrics();
for (Map.Entry<String, Metric> entry : metrics.getMetrics().entrySet()) {
String fullName = mkName(entry.getKey(), metricPath);
if (force && existingMetrics.containsKey(fullName)) {
metricRegistry.remove(fullName);
}
metricRegistry.register(fullName, entry.getValue());
}
}
}
/**
* Remove all metrics from a specified registry.
* @param registry registry name
*/
public void clearRegistry(String registry) {
registry(registry).removeMatching(MetricFilter.ALL);
}
/**
* Remove some metrics from a named registry
* @param registry registry name
* @param metricPath (optional) top-most metric name path elements. If empty then
* this is equivalent to calling {@link #clearRegistry(String)},
* otherwise non-empty elements will be joined using dotted notation
* to form a fully-qualified prefix. Metrics with names that start
* with the prefix will be removed.
* @return set of metrics names that have been removed.
*/
public Set<String> clearMetrics(String registry, String... metricPath) {
PrefixFilter filter;
if (metricPath == null || metricPath.length == 0) {
filter = new PrefixFilter("");
} else {
String prefix = MetricRegistry.name("", metricPath);
filter = new PrefixFilter(prefix);
}
registry(registry).removeMatching(filter);
return filter.getMatched();
}
/**
* Create or get an existing named {@link Meter}
* @param registry registry name
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
* @return existing or a newly created {@link Meter}
*/
public Meter meter(String registry, String metricName, String... metricPath) {
return registry(registry).meter(mkName(metricName, metricPath));
}
/**
* Create or get an existing named {@link Timer}
* @param registry registry name
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
* @return existing or a newly created {@link Timer}
*/
public Timer timer(String registry, String metricName, String... metricPath) {
return registry(registry).timer(mkName(metricName, metricPath));
}
/**
* Create or get an existing named {@link Counter}
* @param registry registry name
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
* @return existing or a newly created {@link Counter}
*/
public Counter counter(String registry, String metricName, String... metricPath) {
return registry(registry).counter(mkName(metricName, metricPath));
}
/**
* Create or get an existing named {@link Histogram}
* @param registry registry name
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
* @return existing or a newly created {@link Histogram}
*/
public Histogram histogram(String registry, String metricName, String... metricPath) {
return registry(registry).histogram(mkName(metricName, metricPath));
}
/**
* Register an instance of {@link Metric}.
* @param registry registry name
* @param metric metric instance
* @param force if true then an already existing metric with the same name will be replaced.
* When false and a metric with the same name already exists an exception
* will be thrown.
* @param metricName metric name, either final name or a fully-qualified name
* using dotted notation
* @param metricPath (optional) additional top-most metric name path elements
*/
public void register(String registry, Metric metric, boolean force, String metricName, String... metricPath) {
MetricRegistry metricRegistry = registry(registry);
String fullName = mkName(metricName, metricPath);
synchronized (metricRegistry) {
if (force && metricRegistry.getMetrics().containsKey(fullName)) {
metricRegistry.remove(fullName);
}
metricRegistry.register(fullName, metric);
}
}
/**
* This method creates a hierarchical name with arbitrary levels of hierarchy
* @param name the final segment of the name, must not be null or empty.
* @param path optional path segments, starting from the top level. Empty or null
* segments will be skipped.
* @return fully-qualified name using dotted notation, with all valid hierarchy
* segments prepended to the name.
*/
public static String mkName(String name, String... path) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("name must not be empty");
}
if (path == null || path.length == 0) {
return name;
} else {
StringBuilder sb = new StringBuilder();
for (String s : path) {
if (s == null || s.isEmpty()) {
continue;
}
if (sb.length() > 0) {
sb.append('.');
}
sb.append(s);
}
if (sb.length() > 0) {
sb.append('.');
}
sb.append(name);
return sb.toString();
}
}
/**
* Allows named registries to be renamed using System properties.
* This would be mostly be useful if you want to combine the metrics from a few registries for a single
* reporter.
* <p>For example, in order to collect metrics from related cores in a single registry you could specify
* the following system properties:</p>
* <pre>
* ... -Dsolr.core.collection1=solr.core.allCollections -Dsolr.core.collection2=solr.core.allCollections
* </pre>
* <b>NOTE:</b> Once a registry is renamed in a way that its metrics are combined with another repository
* it is no longer possible to retrieve the original metrics until this renaming is removed and the Solr
* {@link org.apache.solr.core.SolrInfoMBean.Group} of components that reported to that name is restarted.
* @param registry The name of the registry
* @return A potentially overridden (via System properties) registry name
*/
public static String overridableRegistryName(String registry) {
String fqRegistry = enforcePrefix(registry);
return enforcePrefix(System.getProperty(fqRegistry,fqRegistry));
}
/**
* Enforces the leading {@link #REGISTRY_NAME_PREFIX} in a name.
* @param name input name, possibly without the prefix
* @return original name if it contained the prefix, or the
* input name with the prefix prepended.
*/
public static String enforcePrefix(String name) {
if (name.startsWith(REGISTRY_NAME_PREFIX)) {
return name;
} else {
return new StringBuilder(REGISTRY_NAME_PREFIX).append(name).toString();
}
}
/**
* Helper method to construct a properly prefixed registry name based on the group.
* @param group reporting group
* @param names optional child elements of the registry name. If exactly one element is provided
* and it already contains the required prefix and group name then this value will be used,
* and the group parameter will be ignored.
* @return fully-qualified and prefixed registry name, with overrides applied.
*/
public static String getRegistryName(SolrInfoMBean.Group group, String... names) {
String fullName;
String prefix = REGISTRY_NAME_PREFIX + group.toString() + ".";
// check for existing prefix and group
if (names != null && names.length > 0 && names[0] != null && names[0].startsWith(prefix)) {
// assume the first segment already was expanded
if (names.length > 1) {
String[] newNames = new String[names.length - 1];
System.arraycopy(names, 1, newNames, 0, newNames.length);
fullName = MetricRegistry.name(names[0], newNames);
} else {
fullName = MetricRegistry.name(names[0]);
}
} else {
fullName = MetricRegistry.name(group.toString(), names);
}
return overridableRegistryName(fullName);
}
// reporter management
/**
* Create and register {@link SolrMetricReporter}-s specific to a {@link org.apache.solr.core.SolrInfoMBean.Group}.
* Note: reporters that specify neither "group" nor "registry" attributes are treated as universal -
* they will always be loaded for any group. These two attributes may also contain multiple comma- or
* whitespace-separated values, in which case the reporter will be loaded for any matching value from
* the list. If both attributes are present then only "group" attribute will be processed.
* @param pluginInfos plugin configurations
* @param loader resource loader
* @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) {
if (pluginInfos == null || pluginInfos.length == 0) {
return;
}
String registryName = getRegistryName(group, registryNames);
for (PluginInfo info : pluginInfos) {
String target = info.attributes.get("group");
if (target == null) { // no "group"
target = info.attributes.get("registry");
if (target != null) {
String[] targets = target.split("[\\s,]+");
boolean found = false;
for (String t : targets) {
t = overridableRegistryName(t);
if (registryName.equals(t)) {
found = true;
break;
}
}
if (!found) {
continue;
}
} else {
// neither group nor registry specified.
// always register this plugin for all groups and registries
}
} else { // check groups
String[] targets = target.split("[\\s,]+");
boolean found = false;
for (String t : targets) {
if (group.toString().equals(t)) {
found = true;
break;
}
}
if (!found) {
continue;
}
}
try {
loadReporter(registryName, loader, info);
} catch (Exception e) {
log.warn("Error loading metrics reporter, plugin info: " + info, e);
}
}
}
/**
* Create and register an instance of {@link SolrMetricReporter}.
* @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.
* @throws Exception if any argument is missing or invalid
*/
public void loadReporter(String registry, SolrResourceLoader loader, PluginInfo pluginInfo) 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);
}
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
SolrMetricReporter reporter = loader.newInstance(
pluginInfo.className,
SolrMetricReporter.class,
new String[0],
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);
}
try {
if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
throw new Exception("Could not obtain lock to modify reporters registry: " + registry);
}
} catch (InterruptedException e) {
throw new Exception("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
}
try {
Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
if (perRegistry == null) {
perRegistry = new HashMap<>();
reporters.put(registry, perRegistry);
}
SolrMetricReporter oldReporter = perRegistry.get(pluginInfo.name);
if (oldReporter != null) { // close it
log.info("Replacing existing reporter '" + pluginInfo.name + "' in registry '" + registry + "': " + oldReporter.toString());
oldReporter.close();
}
perRegistry.put(pluginInfo.name, reporter);
} finally {
reportersLock.unlock();
}
}
/**
* Close and unregister a named {@link SolrMetricReporter} for a registry.
* @param registry registry name
* @param name reporter name
* @return true if a named reporter existed and was closed.
*/
public boolean closeReporter(String registry, String name) {
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
try {
if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
log.warn("Could not obtain lock to modify reporters registry: " + registry);
return false;
}
} catch (InterruptedException e) {
log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
return false;
}
try {
Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
if (perRegistry == null) {
return false;
}
SolrMetricReporter reporter = perRegistry.remove(name);
if (reporter == null) {
return false;
}
try {
reporter.close();
} catch (Exception e) {
log.warn("Error closing metric reporter, registry=" + registry + ", name=" + name, e);
}
return true;
} finally {
reportersLock.unlock();
}
}
/**
* Close and unregister all {@link SolrMetricReporter}-s for a registry.
* @param registry registry name
* @return names of closed reporters
*/
public Set<String> closeReporters(String registry) {
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
try {
if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
log.warn("Could not obtain lock to modify reporters registry: " + registry);
return Collections.emptySet();
}
} catch (InterruptedException e) {
log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
return Collections.emptySet();
}
log.info("Closing metric reporters for: " + registry);
try {
Map<String, SolrMetricReporter> perRegistry = reporters.remove(registry);
if (perRegistry != null) {
for (SolrMetricReporter reporter : perRegistry.values()) {
try {
reporter.close();
} catch (IOException ioe) {
log.warn("Exception closing reporter " + reporter, ioe);
}
}
return perRegistry.keySet();
} else {
return Collections.emptySet();
}
} finally {
reportersLock.unlock();
}
}
/**
* Get a map of reporters for a registry. Keys are reporter names, values are reporter instances.
* @param registry registry name
* @return map of reporters and their names, may be empty but never null
*/
public Map<String, SolrMetricReporter> getReporters(String registry) {
// make sure we use a name with prefix, with overrides
registry = overridableRegistryName(registry);
try {
if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
log.warn("Could not obtain lock to modify reporters registry: " + registry);
return Collections.emptyMap();
}
} catch (InterruptedException e) {
log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
return Collections.emptyMap();
}
try {
Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
if (perRegistry == null) {
return Collections.emptyMap();
} else {
// defensive copy - the original map may change after we release the lock
return Collections.unmodifiableMap(new HashMap<>(perRegistry));
}
} finally {
reportersLock.unlock();
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.Collection;
import org.apache.solr.core.SolrInfoMBean;
/**
* Extension of {@link SolrInfoMBean} for use by objects that
* expose metrics through {@link SolrCoreMetricManager}.
*/
public interface SolrMetricProducer extends SolrInfoMBean {
/**
* Initializes metrics specific to this producer
* @param manager an instance of {@link SolrMetricManager}
* @param registry registry name where metrics are registered
* @param scope scope of the metrics (eg. handler name) to separate metrics of
* instances of the same component executing in different contexts
* @return registered (or existing) unqualified names of metrics specific to this producer.
*/
Collection<String> initializeMetrics(SolrMetricManager manager, String registry, String scope);
}

View File

@ -0,0 +1,83 @@
/*
* 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.io.Closeable;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.util.SolrPluginUtils;
import org.apache.solr.util.plugin.PluginInfoInitialized;
/**
* Interface for 'pluggable' metric reporters.
*/
public abstract class SolrMetricReporter implements Closeable, PluginInfoInitialized {
protected final String registryName;
protected final SolrMetricManager metricManager;
protected PluginInfo pluginInfo;
/**
* Create a reporter for metrics managed in a named registry.
* @param registryName registry to use, one of registries managed by
* {@link SolrMetricManager}
*/
protected SolrMetricReporter(SolrMetricManager metricManager, String registryName) {
this.registryName = registryName;
this.metricManager = metricManager;
}
/**
* Initializes a {@link SolrMetricReporter} with the plugin's configuration.
*
* @param pluginInfo the plugin's configuration
*/
@SuppressWarnings("unchecked")
public void init(PluginInfo pluginInfo) {
if (pluginInfo != null) {
this.pluginInfo = pluginInfo.copy();
if (this.pluginInfo.initArgs != null) {
SolrPluginUtils.invokeSetters(this, this.pluginInfo.initArgs);
}
}
validate();
}
/**
* Get the effective {@link PluginInfo} instance that was used for
* initialization of this plugin.
* @return plugin info, or null if not yet initialized.
*/
public PluginInfo getPluginInfo() {
return pluginInfo;
}
/**
* Validates that the reporter has been correctly configured.
*
* @throws IllegalStateException if the reporter is not properly configured
*/
protected abstract void validate() throws IllegalStateException;
@Override
public String toString() {
return getClass().getName() + "{" +
"registryName='" + registryName + '\'' +
", pluginInfo=" + pluginInfo +
'}';
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* The {@link org.apache.solr.metrics.SolrCoreMetricManager} is responsible for
* collecting metrics from {@link org.apache.solr.metrics.SolrMetricProducer}'s
* and exposing metrics to {@link org.apache.solr.metrics.SolrMetricReporter}'s.
*/
package org.apache.solr.metrics;

View File

@ -0,0 +1,284 @@
/*
* 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;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.ObjectNameFactory;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.metrics.SolrMetricInfo;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricReporter;
import org.apache.solr.util.JmxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link SolrMetricReporter} that finds (or creates) a MBeanServer from
* the given configuration and registers metrics to it with JMX.
*/
public class SolrJmxReporter extends SolrMetricReporter {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private String domain;
private String agentId;
private String serviceUrl;
private JmxReporter reporter;
private MBeanServer mBeanServer;
/**
* Creates a new instance of {@link SolrJmxReporter}.
*
* @param registryName name of the registry to report
*/
public SolrJmxReporter(SolrMetricManager metricManager, String registryName) {
super(metricManager, registryName);
setDomain(registryName);
}
/**
* Initializes the reporter by finding (or creating) a MBeanServer
* and registering the metricManager's metric registry.
*
* @param pluginInfo the configuration for the reporter
*/
@Override
public synchronized void init(PluginInfo pluginInfo) {
super.init(pluginInfo);
if (serviceUrl != null && agentId != null) {
ManagementFactory.getPlatformMBeanServer(); // Ensure at least one MBeanServer is available.
mBeanServer = JmxUtil.findFirstMBeanServer();
log.warn("No more than one of serviceUrl(%s) and agentId(%s) should be configured, using first MBeanServer instead of configuration.",
serviceUrl, agentId, mBeanServer);
}
else if (serviceUrl != null) {
try {
mBeanServer = JmxUtil.findMBeanServerForServiceUrl(serviceUrl);
} catch (IOException e) {
log.warn("findMBeanServerForServiceUrl(%s) exception: %s", serviceUrl, e);
mBeanServer = null;
}
}
else if (agentId != null) {
mBeanServer = JmxUtil.findMBeanServerForAgentId(agentId);
} else {
ManagementFactory.getPlatformMBeanServer(); // Ensure at least one MBeanServer is available.
mBeanServer = JmxUtil.findFirstMBeanServer();
log.warn("No serviceUrl or agentId was configured, using first MBeanServer.", mBeanServer);
}
if (mBeanServer == null) {
log.warn("No JMX server found. Not exposing Solr metrics.");
return;
}
JmxObjectNameFactory jmxObjectNameFactory = new JmxObjectNameFactory(pluginInfo.name, domain);
reporter = JmxReporter.forRegistry(metricManager.registry(registryName))
.registerWith(mBeanServer)
.inDomain(domain)
.createsObjectNamesWith(jmxObjectNameFactory)
.build();
reporter.start();
log.info("JMX monitoring enabled at server: " + mBeanServer);
}
/**
* Stops the reporter from publishing metrics.
*/
@Override
public synchronized void close() {
if (reporter != null) {
reporter.close();
reporter = null;
}
}
/**
* Validates that the reporter has been correctly configured.
* Note that all configurable arguments are currently optional.
*
* @throws IllegalStateException if the reporter is not properly configured
*/
@Override
protected void validate() throws IllegalStateException {
// Nothing to validate
}
/**
* Sets the domain with which MBeans are published. If none is set,
* the domain defaults to the name of the core.
*
* @param domain the domain
*/
public void setDomain(String domain) {
if (domain != null) {
this.domain = domain;
} else {
this.domain = registryName;
}
}
/**
* Sets the service url for a JMX server.
* Note that this configuration is optional.
*
* @param serviceUrl the service url
*/
public void setServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
}
/**
* Sets the agent id for a JMX server.
* Note that this configuration is optional.
*
* @param agentId the agent id
*/
public void setAgentId(String agentId) {
this.agentId = agentId;
}
/**
* Retrieves the reporter's MBeanServer.
*
* @return the reporter's MBeanServer
*/
public MBeanServer getMBeanServer() {
return mBeanServer;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH, "[%s@%s: domain = %s, service url = %s, agent id = %s]",
getClass().getName(), Integer.toHexString(hashCode()), domain, serviceUrl, agentId);
}
/**
* Factory to create MBean names for a given metric.
*/
private static class JmxObjectNameFactory implements ObjectNameFactory {
private final String domain;
private final String[] subdomains;
private final String reporterName;
JmxObjectNameFactory(String reporterName, String domain) {
this.reporterName = reporterName;
this.domain = domain;
this.subdomains = domain.split("\\.");
}
/**
* Create a hierarchical name of a metric.
*
* @param type metric class, eg. "counters"
* @param currentDomain JMX domain
* @param name metric name
*/
@Override
public ObjectName createName(String type, String currentDomain, String name) {
SolrMetricInfo metricInfo = SolrMetricInfo.of(name);
// It turns out that ObjectName(String) mostly preserves key ordering
// as specified in the constructor (except for the 'type' key that ends
// up at top level) - unlike ObjectName(String, Map) constructor
// that seems to have a mind of its own...
StringBuilder sb = new StringBuilder();
if (domain.equals(currentDomain)) {
if (subdomains != null && subdomains.length > 1) {
// use only first segment as domain
sb.append(subdomains[0]);
sb.append(':');
// use remaining segments as properties
for (int i = 1; i < subdomains.length; i++) {
if (i > 1) {
sb.append(',');
}
sb.append("dom");
sb.append(String.valueOf(i));
sb.append('=');
sb.append(subdomains[i]);
}
sb.append(','); // separate from other properties
} else {
sb.append(currentDomain);
sb.append(':');
}
} else {
sb.append(currentDomain);
sb.append(':');
}
sb.append("reporter=");
sb.append(reporterName);
sb.append(',');
if (metricInfo != null) {
sb.append("category=");
sb.append(metricInfo.category.toString());
sb.append(",scope=");
sb.append(metricInfo.scope);
// we could also split by type, but don't call it 'type' :)
// sb.append(",class=");
//sb.append(type);
sb.append(",name=");
sb.append(metricInfo.name);
} else {
// make dotted names into hierarchies
String[] path = name.split("\\.");
for (int i = 0; i < path.length - 1; i++) {
if (i > 0) {
sb.append(',');
}
sb.append("name"); sb.append(String.valueOf(i));
sb.append('=');
sb.append(path[i]);
}
if (path.length > 1) {
sb.append(',');
}
// split by type
// sb.append("class=");
// sb.append(type);
sb.append("name=");
sb.append(path[path.length - 1]);
}
ObjectName objectName;
try {
objectName = new ObjectName(sb.toString());
} catch (MalformedObjectNameException e) {
throw new RuntimeException(sb.toString(), e);
}
return objectName;
}
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
/**
* This package houses 'pluggable' metric reporters that
* inherit from the {@link org.apache.solr.metrics.SolrMetricReporter} class.
*/
package org.apache.solr.metrics.reporters;

View File

@ -46,6 +46,7 @@ public interface PermissionNameProvider {
SCHEMA_EDIT_PERM("schema-edit", "*"),
SECURITY_EDIT_PERM("security-edit", null),
SECURITY_READ_PERM("security-read", null),
METRICS_READ_PERM("metrics-read", null),
ALL("all", unmodifiableSet(new HashSet<>(asList("*", null))))
;
final String name;

View File

@ -16,6 +16,7 @@
*/
package org.apache.solr.servlet;
import javax.management.MBeanServer;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
@ -33,6 +34,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
@ -45,6 +47,12 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.io.output.CloseShieldOutputStream;
@ -58,8 +66,10 @@ import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.security.PKIAuthenticationPlugin;
@ -157,6 +167,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
this.cores = createCoreContainer(solrHome == null ? SolrResourceLoader.locateSolrHome() : Paths.get(solrHome),
extraProperties);
this.httpClient = cores.getUpdateShardHandler().getHttpClient();
setupJvmMetrics();
log.debug("user.dir=" + System.getProperty("user.dir"));
}
catch( Throwable t ) {
@ -171,6 +182,22 @@ public class SolrDispatchFilter extends BaseSolrFilter {
log.trace("SolrDispatchFilter.init() done");
}
private void setupJvmMetrics() {
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
SolrMetricManager metricManager = cores.getMetricManager();
try {
String registry = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm);
metricManager.registerAll(registry, new BufferPoolMetricSet(platformMBeanServer), true, "bufferPools");
metricManager.registerAll(registry, new ClassLoadingGaugeSet(), true, "classLoading");
metricManager.register(registry, new FileDescriptorRatioGauge(), true, "fileDescriptorRatio");
metricManager.registerAll(registry, new GarbageCollectorMetricSet(), true, "gc");
metricManager.registerAll(registry, new MemoryUsageGaugeSet(), true, "memory");
metricManager.registerAll(registry, new ThreadStatesGaugeSet(), true, "threads"); // todo should we use CachedThreadStatesGaugeSet instead?
} catch (Exception e) {
log.warn("Error registering JVM metrics", e);
}
}
private void logWelcomeBanner() {
log.info(" ___ _ Welcome to Apache Solr™ version {}", solrVersion());
log.info("/ __| ___| |_ _ Starting in {} mode on port {}", isCloudMode() ? "cloud" : "standalone", getSolrPort());

View File

@ -0,0 +1,78 @@
/*
* 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.util;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.util.List;
/**
* Utility methods to find a MBeanServer.
*
* This was factored out from {@link org.apache.solr.core.JmxMonitoredMap}
* and can eventually replace the logic used there.
*/
public final class JmxUtil {
/**
* Retrieve the first MBeanServer found.
*
* @return the first MBeanServer found
*/
public static MBeanServer findFirstMBeanServer() {
return findMBeanServerForAgentId(null);
}
/**
* Find a MBeanServer given a service url.
*
* @param serviceUrl the service url
* @return a MBeanServer
*/
public static MBeanServer findMBeanServerForServiceUrl(String serviceUrl) throws IOException {
if (serviceUrl == null) {
return null;
}
MBeanServer server = MBeanServerFactory.newMBeanServer();
JMXConnectorServer connector = JMXConnectorServerFactory
.newJMXConnectorServer(new JMXServiceURL(serviceUrl), null, server);
connector.start();
return server;
}
/**
* Find a MBeanServer given an agent id.
*
* @param agentId the agent id
* @return a MBeanServer
*/
public static MBeanServer findMBeanServerForAgentId(String agentId) {
List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(agentId);
if (servers == null || servers.isEmpty()) {
return null;
}
return servers.get(0);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.util.stats;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import org.apache.solr.common.util.NamedList;
/**
* Metrics specific utility functions.
*/
public class MetricUtils {
/**
* Adds metrics from a Timer to a NamedList, using well-known names.
* @param lst The NamedList to add the metrics data to
* @param timer The Timer to extract the metrics from
*/
public static void addMetrics(NamedList<Object> lst, Timer timer) {
Snapshot snapshot = timer.getSnapshot();
lst.add("avgRequestsPerSecond", timer.getMeanRate());
lst.add("5minRateRequestsPerSecond", timer.getFiveMinuteRate());
lst.add("15minRateRequestsPerSecond", timer.getFifteenMinuteRate());
lst.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
lst.add("medianRequestTime", nsToMs(snapshot.getMedian()));
lst.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
lst.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
lst.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
lst.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
}
/**
* Converts a double representing nanoseconds to a double representing milliseconds.
*
* @param ns the amount of time in nanoseconds
* @return the amount of time in milliseconds
*/
static double nsToMs(double ns) {
return ns / TimeUnit.MILLISECONDS.toNanos(1);
}
/**
* Returns a NamedList respresentation of the given metric registry. Only those metrics
* are converted to NamedList which match at least one of the given MetricFilter instances.
*
* @param registry the {@link MetricRegistry} to be converted to NamedList
* @param metricFilters a list of {@link MetricFilter} instances
* @return a {@link NamedList}
*/
public static NamedList toNamedList(MetricRegistry registry, List<MetricFilter> metricFilters) {
NamedList response = new NamedList();
Map<String, Metric> metrics = registry.getMetrics();
SortedSet<String> names = registry.getNames();
names.stream().filter(s -> metricFilters.stream().anyMatch(metricFilter -> metricFilter.matches(s, metrics.get(s)))).forEach(n -> {
Metric metric = metrics.get(n);
if (metric instanceof Counter) {
Counter counter = (Counter) metric;
response.add(n, counterToNamedList(counter));
} else if (metric instanceof Gauge) {
Gauge gauge = (Gauge) metric;
response.add(n, gaugeToNamedList(gauge));
} else if (metric instanceof Meter) {
Meter meter = (Meter) metric;
response.add(n, meterToNamedList(meter));
} else if (metric instanceof Timer) {
Timer timer = (Timer) metric;
response.add(n, timerToNamedList(timer));
} else if (metric instanceof Histogram) {
Histogram histogram = (Histogram) metric;
response.add(n, histogramToNamedList(histogram));
}
});
return response;
}
static NamedList histogramToNamedList(Histogram histogram) {
NamedList response = new NamedList();
Snapshot snapshot = histogram.getSnapshot();
response.add("requests", histogram.getCount());
response.add("minTime", nsToMs(snapshot.getMin()));
response.add("maxTime", nsToMs(snapshot.getMax()));
response.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
response.add("medianRequestTime", nsToMs(snapshot.getMedian()));
response.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
response.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
response.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
response.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
return response;
}
static NamedList timerToNamedList(Timer timer) {
NamedList response = new NamedList();
addMetrics(response, timer);
return response;
}
static NamedList meterToNamedList(Meter meter) {
NamedList response = new NamedList();
response.add("requests", meter.getCount());
response.add("avgRequestsPerSecond", meter.getMeanRate());
response.add("1minRateRequestsPerSecond", meter.getOneMinuteRate());
response.add("5minRateRequestsPerSecond", meter.getFiveMinuteRate());
response.add("15minRateRequestsPerSecond", meter.getFifteenMinuteRate());
return response;
}
static NamedList gaugeToNamedList(Gauge gauge) {
NamedList response = new NamedList();
response.add("value", gauge.getValue());
return response;
}
static NamedList counterToNamedList(Counter counter) {
NamedList response = new NamedList();
response.add("requests", counter.getCount());
return response;
}
}

View File

@ -1,58 +0,0 @@
/*
* 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.util.stats;
import java.util.concurrent.TimeUnit;
import com.codahale.metrics.Snapshot;
import com.codahale.metrics.Timer;
import org.apache.solr.common.util.NamedList;
/**
* Solr specific {@link Timer} utility functions.
*/
public class TimerUtils {
/**
* Adds metrics from a Timer to a NamedList, using well-known names.
* @param lst The NamedList to add the metrics data to
* @param timer The Timer to extract the metrics from
*/
public static void addMetrics(NamedList<Object> lst, Timer timer) {
Snapshot snapshot = timer.getSnapshot();
lst.add("avgRequestsPerSecond", timer.getMeanRate());
lst.add("5minRateRequestsPerSecond", timer.getFiveMinuteRate());
lst.add("15minRateRequestsPerSecond", timer.getFifteenMinuteRate());
lst.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
lst.add("medianRequestTime", nsToMs(snapshot.getMedian()));
lst.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
lst.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
lst.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
lst.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
}
/**
* Converts a double representing nanoseconds to a double representing milliseconds.
*
* @param ns the amount of time in nanoseconds
* @return the amount of time in milliseconds
*/
static double nsToMs(double ns) {
return ns / TimeUnit.MILLISECONDS.toNanos(1);
}
}

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
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.
-->
<solr>
<metrics>
<!-- this reporter doesn't specify 'group' or 'registry', it will be instantiated for any group. -->
<reporter name="universal" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<!-- this reporter will be created for both "node" and "core" groups -->
<reporter name="multigroup" group="node, core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<!-- this reporter will be created for both "node" and "core.collection1" registries -->
<reporter name="multiregistry" registry="node, core.collection1" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<reporter name="reporter1" group="jvm" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<reporter name="reporter1" group="jetty" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<reporter name="reporter1" group="http" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<reporter name="reporter1" group="node" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<!-- core reporters. -->
<reporter name="reporter1" group="core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<reporter name="reporter2" group="core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
<!-- reporter for a specific registry -->
<reporter name="specific" registry="core.collection1" class="org.apache.solr.metrics.reporters.MockMetricReporter">
<str name="configurable">configured</str>
</reporter>
</metrics>
</solr>

View File

@ -0,0 +1,97 @@
/*
* 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 org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Test for {@link MetricsHandler}
*/
public class MetricsHandlerTest extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig.xml", "schema.xml");
}
@Test
public void test() throws Exception {
MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
SolrQueryResponse resp = new SolrQueryResponse();
handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json"), resp);
NamedList values = resp.getValues();
assertNotNull(values.get("metrics"));
values = (NamedList) values.get("metrics");
System.out.println(values);
assertNotNull(values.get("solr.jetty"));
assertNotNull(values.get("solr.jvm"));
assertNotNull(values.get("solr.http"));
assertNotNull(values.get("solr.node"));
NamedList nl = (NamedList) values.get("solr.core.collection1");
assertNotNull(nl);
assertNotNull(nl.get("newSearcherErrors")); // counter type
assertNotNull(((NamedList) nl.get("newSearcherErrors")).get("requests"));
assertEquals(0L, ((NamedList) nl.get("newSearcherErrors")).get("requests"));
nl = (NamedList) values.get("solr.node");
assertNotNull(nl.get("cores.loaded")); // int gauge
assertEquals(1, ((NamedList) nl.get("cores.loaded")).get("value"));
assertNotNull(nl.get("QUERYHANDLER./admin/authorization.clientErrors")); // timer type
assertEquals(5, ((NamedList) nl.get("QUERYHANDLER./admin/authorization.clientErrors")).size());
resp = new SolrQueryResponse();
handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm,jetty"), resp);
values = resp.getValues();
assertNotNull(values.get("metrics"));
values = (NamedList) values.get("metrics");
assertEquals(2, values.size());
assertNotNull(values.get("solr.jetty"));
assertNotNull(values.get("solr.jvm"));
resp = new SolrQueryResponse();
handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm,jetty"), resp);
values = resp.getValues();
assertNotNull(values.get("metrics"));
values = (NamedList) values.get("metrics");
assertEquals(2, values.size());
assertNotNull(values.get("solr.jetty"));
assertNotNull(values.get("solr.jvm"));
resp = new SolrQueryResponse();
handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm", "group", "jetty"), resp);
values = resp.getValues();
assertNotNull(values.get("metrics"));
values = (NamedList) values.get("metrics");
assertEquals(2, values.size());
assertNotNull(values.get("solr.jetty"));
assertNotNull(values.get("solr.jvm"));
resp = new SolrQueryResponse();
handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "node", "type", "counter"), resp);
values = resp.getValues();
assertNotNull(values.get("metrics"));
values = (NamedList) values.get("metrics");
assertEquals(1, values.size());
assertNotNull(values.get("solr.node"));
assertNull(values.get("QUERYHANDLER./admin/authorization.errors")); // this is a timer node
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.metrics.reporters.MockMetricReporter;
import org.apache.solr.schema.FieldType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class SolrCoreMetricManagerTest extends SolrTestCaseJ4 {
private static final int MAX_ITERATIONS = 100;
private SolrCoreMetricManager coreMetricManager;
private SolrMetricManager metricManager;
@Before
public void beforeTest() throws Exception {
initCore("solrconfig-basic.xml", "schema.xml");
coreMetricManager = h.getCore().getCoreMetricManager();
metricManager = h.getCore().getCoreDescriptor().getCoreContainer().getMetricManager();
}
@After
public void afterTest() throws IOException {
coreMetricManager.close();
assertTrue(metricManager.getReporters(coreMetricManager.getRegistryName()).isEmpty());
deleteCore();
}
@Test
public void testRegisterMetrics() {
Random random = random();
String scope = SolrMetricTestUtils.getRandomScope(random);
SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random);
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random);
SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
try {
coreMetricManager.registerMetricProducer(scope, producer);
assertNotNull(scope);
assertNotNull(category);
assertNotNull(metrics);
assertRegistered(scope, metrics, coreMetricManager);
} catch (final IllegalArgumentException e) {
assertTrue("expected at least one null but got: scope="+scope+" category="+category+" metrics="+metrics,
(scope == null || category == null || metrics == null));
assertRegistered(scope, new HashMap<>(), coreMetricManager);
}
}
@Test
public void testRegisterMetricsWithReplacements() {
Random random = random();
Map<String, Counter> registered = new HashMap<>();
String scope = SolrMetricTestUtils.getRandomScope(random, true);
SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
for (int i = 0; i < iterations; ++i) {
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetricsWithReplacements(random, registered);
if (metrics.isEmpty()) {
continue;
}
SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
coreMetricManager.registerMetricProducer(scope, producer);
registered.putAll(metrics);
assertRegistered(scope, registered, coreMetricManager);
}
}
@Test
public void testLoadReporter() throws Exception {
Random random = random();
String className = MockMetricReporter.class.getName();
String reporterName = TestUtil.randomUnicodeString(random);
Map<String, Object> attrs = new HashMap<>();
attrs.put(FieldType.CLASS_NAME, className);
attrs.put(CoreAdminParams.NAME, reporterName);
boolean shouldDefineConfigurable = random.nextBoolean();
String configurable = TestUtil.randomUnicodeString(random);
if (shouldDefineConfigurable) attrs.put("configurable", configurable);
boolean shouldDefinePlugin = random.nextBoolean();
PluginInfo pluginInfo = shouldDefinePlugin ? new PluginInfo(TestUtil.randomUnicodeString(random), attrs) : null;
try {
metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
assertNotNull(pluginInfo);
Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
assertTrue("reporters.size should be > 0, but was + " + reporters.size(), reporters.size() > 0);
assertNotNull("reporter " + reporterName + " not present among " + reporters, reporters.get(reporterName));
assertTrue("wrong reporter class: " + reporters.get(reporterName), reporters.get(reporterName) instanceof MockMetricReporter);
} catch (IllegalArgumentException e) {
assertTrue(pluginInfo == null || attrs.get("configurable") == null);
assertNull(metricManager.getReporters(coreMetricManager.getRegistryName()).get(reporterName));
}
}
private void assertRegistered(String scope, Map<String, Counter> newMetrics, SolrCoreMetricManager coreMetricManager) {
if (scope == null) {
return;
}
String filter = "." + scope + ".";
MetricRegistry registry = metricManager.registry(coreMetricManager.getRegistryName());
assertEquals(newMetrics.size(), registry.getMetrics().
keySet().stream().filter(s -> s.contains(filter)).count());
Map<String, Metric> registeredMetrics = registry.getMetrics().
entrySet().stream().filter(e -> e.getKey() != null && e.getKey().contains(filter)).
collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
for (Map.Entry<String, Metric> entry : registeredMetrics.entrySet()) {
String name = entry.getKey();
Metric expectedMetric = entry.getValue();
Metric actualMetric = registry.getMetrics().get(name);
assertNotNull(actualMetric);
assertEquals(expectedMetric, actualMetric);
}
}
@Test
public void testRegistryName() throws Exception {
String collectionName = "my_collection_";
String cloudCoreName = "my_collection__shard1_0_replica0";
String simpleCoreName = "collection_1_replica0";
String simpleRegistryName = "solr.core." + simpleCoreName;
String cloudRegistryName = "solr.core." + cloudCoreName;
String nestedRegistryName = "solr.core.my_collection_.shard1_0.replica0";
// pass through
assertEquals(cloudRegistryName, coreMetricManager.createRegistryName(null, cloudCoreName));
assertEquals(simpleRegistryName, coreMetricManager.createRegistryName(null, simpleCoreName));
// unknown naming scheme -> pass through
assertEquals(simpleRegistryName, coreMetricManager.createRegistryName(collectionName, simpleCoreName));
// cloud collection
assertEquals(nestedRegistryName, coreMetricManager.createRegistryName(collectionName, cloudCoreName));
}
}

View File

@ -0,0 +1,273 @@
/*
* 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.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.metrics.reporters.MockMetricReporter;
import org.junit.Test;
public class SolrMetricManagerTest extends SolrTestCaseJ4 {
@Test
public void testOverridableRegistryName() throws Exception {
Random r = random();
String originalName = TestUtil.randomSimpleString(r, 1, 10);
String targetName = TestUtil.randomSimpleString(r, 1, 10);
// no override
String result = SolrMetricManager.overridableRegistryName(originalName);
assertEquals(SolrMetricManager.REGISTRY_NAME_PREFIX + originalName, result);
// with override
System.setProperty(SolrMetricManager.REGISTRY_NAME_PREFIX + originalName, targetName);
result = SolrMetricManager.overridableRegistryName(originalName);
assertEquals(SolrMetricManager.REGISTRY_NAME_PREFIX + targetName, result);
}
@Test
public void testMoveMetrics() throws Exception {
Random r = random();
SolrMetricManager metricManager = new SolrMetricManager();
Map<String, Counter> metrics1 = SolrMetricTestUtils.getRandomMetrics(r, true);
Map<String, Counter> metrics2 = SolrMetricTestUtils.getRandomMetrics(r, true);
String fromName = TestUtil.randomSimpleString(r, 1, 10);
String toName = TestUtil.randomSimpleString(r, 1, 10);
// register test metrics
for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics1");
}
for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics2");
}
assertEquals(metrics1.size() + metrics2.size(), metricManager.registry(fromName).getMetrics().size());
// move metrics1
metricManager.moveMetrics(fromName, toName, new SolrMetricManager.PrefixFilter("metrics1"));
// check the remaining metrics
Map<String, Metric> fromMetrics = metricManager.registry(fromName).getMetrics();
assertEquals(metrics2.size(), fromMetrics.size());
for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
Object value = fromMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics2"));
assertNotNull(value);
assertEquals(entry.getValue(), value);
}
// check the moved metrics
Map<String, Metric> toMetrics = metricManager.registry(toName).getMetrics();
assertEquals(metrics1.size(), toMetrics.size());
for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
Object value = toMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics1"));
assertNotNull(value);
assertEquals(entry.getValue(), value);
}
// move all remaining metrics
metricManager.moveMetrics(fromName, toName, null);
fromMetrics = metricManager.registry(fromName).getMetrics();
assertEquals(0, fromMetrics.size());
toMetrics = metricManager.registry(toName).getMetrics();
assertEquals(metrics1.size() + metrics2.size(), toMetrics.size());
}
@Test
public void testRegisterAll() throws Exception {
Random r = random();
SolrMetricManager metricManager = new SolrMetricManager();
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
MetricRegistry mr = new MetricRegistry();
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
mr.register(entry.getKey(), entry.getValue());
}
String registryName = TestUtil.randomSimpleString(r, 1, 10);
assertEquals(0, metricManager.registry(registryName).getMetrics().size());
metricManager.registerAll(registryName, mr, false);
// this should simply skip existing names
metricManager.registerAll(registryName, mr, true);
// this should produce error
try {
metricManager.registerAll(registryName, mr, false);
fail("registerAll with duplicate metric names should fail");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testClearMetrics() throws Exception {
Random r = random();
SolrMetricManager metricManager = new SolrMetricManager();
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
String registryName = TestUtil.randomSimpleString(r, 1, 10);
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo", "bar");
}
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo", "baz");
}
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo");
}
assertEquals(metrics.size() * 3, metricManager.registry(registryName).getMetrics().size());
// clear "foo.bar"
Set<String> removed = metricManager.clearMetrics(registryName, "foo", "bar");
assertEquals(metrics.size(), removed.size());
for (String s : removed) {
assertTrue(s.startsWith("foo.bar."));
}
removed = metricManager.clearMetrics(registryName, "foo", "baz");
assertEquals(metrics.size(), removed.size());
for (String s : removed) {
assertTrue(s.startsWith("foo.baz."));
}
// perhaps surprisingly, this works too - see PrefixFilter docs
removed = metricManager.clearMetrics(registryName, "fo");
assertEquals(metrics.size(), removed.size());
for (String s : removed) {
assertTrue(s.startsWith("foo."));
}
}
@Test
public void testSimpleMetrics() throws Exception {
Random r = random();
SolrMetricManager metricManager = new SolrMetricManager();
String registryName = TestUtil.randomSimpleString(r, 1, 10);
metricManager.counter(registryName, "simple_counter", "foo", "bar");
metricManager.timer(registryName, "simple_timer", "foo", "bar");
metricManager.meter(registryName, "simple_meter", "foo", "bar");
metricManager.histogram(registryName, "simple_histogram", "foo", "bar");
Map<String, Metric> metrics = metricManager.registry(registryName).getMetrics();
assertEquals(4, metrics.size());
for (Map.Entry<String, Metric> entry : metrics.entrySet()) {
assertTrue(entry.getKey().startsWith("foo.bar.simple_"));
}
}
@Test
public void testRegistryName() throws Exception {
Random r = random();
String name = TestUtil.randomSimpleString(r, 1, 10);
String result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, name, "collection1");
assertEquals("solr.core." + name + ".collection1", result);
// try it with already prefixed name - group will be ignored
result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, result);
assertEquals("solr.core." + name + ".collection1", result);
// try it with already prefixed name but with additional segments
result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, result, "shard1", "replica1");
assertEquals("solr.core." + name + ".collection1.shard1.replica1", result);
}
@Test
public void testReporters() throws Exception {
Random r = random();
SolrResourceLoader loader = new SolrResourceLoader();
SolrMetricManager metricManager = new SolrMetricManager();
PluginInfo[] plugins = new PluginInfo[] {
createPluginInfo("universal_foo", null, null),
createPluginInfo("multigroup_foo", "jvm, node, core", null),
createPluginInfo("multiregistry_foo", null, "solr.node, solr.core.collection1"),
createPluginInfo("specific_foo", null, "solr.core.collection1"),
createPluginInfo("node_foo", "node", null),
createPluginInfo("core_foo", "core", null)
};
metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.node);
Map<String, SolrMetricReporter> reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
assertEquals(4, reporters.size());
assertTrue(reporters.containsKey("universal_foo"));
assertTrue(reporters.containsKey("multigroup_foo"));
assertTrue(reporters.containsKey("node_foo"));
assertTrue(reporters.containsKey("multiregistry_foo"));
metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.core, "collection1");
reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, "collection1"));
assertEquals(5, reporters.size());
assertTrue(reporters.containsKey("universal_foo"));
assertTrue(reporters.containsKey("multigroup_foo"));
assertTrue(reporters.containsKey("specific_foo"));
assertTrue(reporters.containsKey("core_foo"));
assertTrue(reporters.containsKey("multiregistry_foo"));
metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.jvm);
reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm));
assertEquals(2, reporters.size());
assertTrue(reporters.containsKey("universal_foo"));
assertTrue(reporters.containsKey("multigroup_foo"));
metricManager.removeRegistry("solr.jvm");
reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm));
assertEquals(0, reporters.size());
metricManager.removeRegistry("solr.node");
reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
assertEquals(0, reporters.size());
metricManager.removeRegistry("solr.core.collection1");
reporters = metricManager.getReporters(
SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, "collection1"));
assertEquals(0, reporters.size());
}
private PluginInfo createPluginInfo(String name, String group, String registry) {
Map<String,String> attrs = new HashMap<>();
attrs.put("name", name);
attrs.put("class", MockMetricReporter.class.getName());
if (group != null) {
attrs.put("group", group);
}
if (registry != null) {
attrs.put("registry", registry);
}
NamedList initArgs = new NamedList();
initArgs.add("configurable", "true");
return new PluginInfo("SolrMetricReporter", attrs, initArgs, null);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.HashMap;
import java.util.Map;
import java.util.Random;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.metrics.reporters.MockMetricReporter;
import org.apache.solr.schema.FieldType;
import org.junit.Test;
public class SolrMetricReporterTest extends LuceneTestCase {
@Test
public void testInit() throws Exception {
Random random = random();
SolrMetricManager metricManager = new SolrMetricManager();
final String registryName = TestUtil.randomSimpleString(random);
final MockMetricReporter reporter = new MockMetricReporter(metricManager, registryName);
Map<String, Object> attrs = new HashMap<>();
attrs.put(FieldType.CLASS_NAME, MockMetricReporter.class.getName());
attrs.put(CoreAdminParams.NAME, TestUtil.randomUnicodeString(random));
boolean shouldDefineConfigurable = random.nextBoolean();
String configurable = TestUtil.randomUnicodeString(random);
if (shouldDefineConfigurable) attrs.put("configurable", configurable);
boolean shouldDefinePlugin = random.nextBoolean();
String type = TestUtil.randomUnicodeString(random);
PluginInfo pluginInfo = shouldDefinePlugin ? new PluginInfo(type, attrs) : null;
try {
reporter.init(pluginInfo);
assertNotNull(pluginInfo);
assertEquals(configurable, attrs.get("configurable"));
assertTrue(reporter.didValidate);
assertNotNull(reporter.configurable);
assertEquals(configurable, reporter.configurable);
} catch (IllegalStateException e) {
assertTrue(pluginInfo == null || attrs.get("configurable") == null);
assertTrue(reporter.didValidate);
assertNull(reporter.configurable);
} finally {
reporter.close();
}
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import com.codahale.metrics.Counter;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrInfoMBean;
public final class SolrMetricTestUtils {
private static final int MAX_ITERATIONS = 100;
private static final SolrInfoMBean.Category CATEGORIES[] = SolrInfoMBean.Category.values();
public static String getRandomScope(Random random) {
return getRandomScope(random, random.nextBoolean());
}
public static String getRandomScope(Random random, boolean shouldDefineScope) {
return shouldDefineScope ? TestUtil.randomSimpleString(random, 1, 10) : null; // must be simple string for JMX publishing
}
public static SolrInfoMBean.Category getRandomCategory(Random random) {
return getRandomCategory(random, random.nextBoolean());
}
public static SolrInfoMBean.Category getRandomCategory(Random random, boolean shouldDefineCategory) {
return shouldDefineCategory ? CATEGORIES[TestUtil.nextInt(random, 0, CATEGORIES.length - 1)] : null;
}
public static Map<String, Counter> getRandomMetrics(Random random) {
return getRandomMetrics(random, random.nextBoolean());
}
public static Map<String, Counter> getRandomMetrics(Random random, boolean shouldDefineMetrics) {
return shouldDefineMetrics ? getRandomMetricsWithReplacements(random, new HashMap<>()) : null;
}
public static final String SUFFIX = "_testing";
public static Map<String, Counter> getRandomMetricsWithReplacements(Random random, Map<String, Counter> existing) {
HashMap<String, Counter> metrics = new HashMap<>();
ArrayList<String> existingKeys = new ArrayList<>(existing.keySet());
int numMetrics = TestUtil.nextInt(random, 1, MAX_ITERATIONS);
for (int i = 0; i < numMetrics; ++i) {
boolean shouldReplaceMetric = !existing.isEmpty() && random.nextBoolean();
String name = shouldReplaceMetric
? existingKeys.get(TestUtil.nextInt(random, 0, existingKeys.size() - 1))
: TestUtil.randomSimpleString(random, 1, 10) + SUFFIX; // must be simple string for JMX publishing
Counter counter = new Counter();
counter.inc(random.nextLong());
metrics.put(name, counter);
}
return metrics;
}
public static SolrMetricProducer getProducerOf(SolrMetricManager metricManager, SolrInfoMBean.Category category, String scope, Map<String, Counter> metrics) {
return new SolrMetricProducer() {
@Override
public Collection<String> initializeMetrics(SolrMetricManager manager, String registry, String scope) {
if (metrics == null || metrics.isEmpty()) {
return Collections.emptyList();
}
for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
manager.counter(registry, entry.getKey(), category.toString(), scope);
}
return metrics.keySet();
}
@Override
public String getName() {
return scope;
}
@Override
public String getVersion() {
return "0.0";
}
@Override
public String getDescription() {
return "foo";
}
@Override
public Category getCategory() {
return category;
}
@Override
public String getSource() {
return null;
}
@Override
public URL[] getDocs() {
return new URL[0];
}
@Override
public NamedList getStatistics() {
return null;
}
@Override
public String toString() {
return "SolrMetricProducer.of{" +
"\ncategory=" + category +
"\nscope=" + scope +
"\nmetrics=" + metrics +
"\n}";
}
};
}
}

View File

@ -0,0 +1,143 @@
/*
* 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.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import com.codahale.metrics.Timer;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.metrics.reporters.MockMetricReporter;
import org.apache.solr.util.TestHarness;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
private static final int MAX_ITERATIONS = 20;
private static final String CORE_NAME = "metrics_integration";
private static final String METRIC_NAME = "requestTimes";
private static final String HANDLER_NAME = "standard";
private static final String[] REPORTER_NAMES = {"reporter1", "reporter2"};
private static final String UNIVERSAL = "universal";
private static final String SPECIFIC = "specific";
private static final String MULTIGROUP = "multigroup";
private static final String MULTIREGISTRY = "multiregistry";
private static final String[] INITIAL_REPORTERS = {REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, SPECIFIC, MULTIGROUP, MULTIREGISTRY};
private static final String[] RENAMED_REPORTERS = {REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, MULTIGROUP};
private static final SolrInfoMBean.Category HANDLER_CATEGORY = SolrInfoMBean.Category.QUERYHANDLER;
private CoreContainer cc;
private SolrMetricManager metricManager;
@Before
public void beforeTest() throws Exception {
Path home = Paths.get(TEST_HOME());
// define these properties, they are used in solrconfig.xml
System.setProperty("solr.test.sys.prop1", "propone");
System.setProperty("solr.test.sys.prop2", "proptwo");
String solrXml = FileUtils.readFileToString(Paths.get(home.toString(), "solr-metricreporter.xml").toFile(), "UTF-8");
NodeConfig cfg = SolrXmlConfig.fromString(new SolrResourceLoader(home), solrXml);
cc = createCoreContainer(cfg,
new TestHarness.TestCoresLocator(DEFAULT_TEST_CORENAME, initCoreDataDir.getAbsolutePath(), "solrconfig.xml", "schema.xml"));
h.coreName = DEFAULT_TEST_CORENAME;
metricManager = cc.getMetricManager();
// initially there are more reporters, because two of them are added via a matching collection name
Map<String, SolrMetricReporter> reporters = metricManager.getReporters("solr.core." + DEFAULT_TEST_CORENAME);
assertEquals(INITIAL_REPORTERS.length, reporters.size());
assertTrue(reporters.keySet().containsAll(Arrays.asList(INITIAL_REPORTERS)));
// test rename operation
cc.rename(DEFAULT_TEST_CORENAME, CORE_NAME);
h.coreName = CORE_NAME;
cfg = cc.getConfig();
PluginInfo[] plugins = cfg.getMetricReporterPlugins();
assertNotNull(plugins);
assertEquals(10, plugins.length);
reporters = metricManager.getReporters("solr.node");
assertEquals(4, reporters.size());
assertTrue("Reporter '" + REPORTER_NAMES[0] + "' missing in solr.node", reporters.containsKey(REPORTER_NAMES[0]));
assertTrue("Reporter '" + UNIVERSAL + "' missing in solr.node", reporters.containsKey(UNIVERSAL));
assertTrue("Reporter '" + MULTIGROUP + "' missing in solr.node", reporters.containsKey(MULTIGROUP));
assertTrue("Reporter '" + MULTIREGISTRY + "' missing in solr.node", reporters.containsKey(MULTIREGISTRY));
SolrMetricReporter reporter = reporters.get(REPORTER_NAMES[0]);
assertTrue("Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
reporter instanceof MockMetricReporter);
reporter = reporters.get(UNIVERSAL);
assertTrue("Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
reporter instanceof MockMetricReporter);
}
@After
public void afterTest() throws Exception {
SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
deleteCore();
for (String reporterName : RENAMED_REPORTERS) {
SolrMetricReporter reporter = reporters.get(reporterName);
MockMetricReporter mockReporter = (MockMetricReporter) reporter;
assertTrue("Reporter " + reporterName + " was not closed: " + mockReporter, mockReporter.didClose);
}
}
@Test
public void testConfigureReporter() throws Exception {
Random random = random();
String metricName = SolrMetricManager.mkName(METRIC_NAME, HANDLER_CATEGORY.toString(), HANDLER_NAME);
SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
Timer timer = (Timer) metricManager.timer(coreMetricManager.getRegistryName(), metricName);
long initialCount = timer.getCount();
int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
for (int i = 0; i < iterations; ++i) {
h.query(req("*"));
}
long finalCount = timer.getCount();
assertEquals("metric counter incorrect", iterations, finalCount - initialCount);
Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
assertEquals(RENAMED_REPORTERS.length, reporters.size());
// SPECIFIC and MULTIREGISTRY were skipped because they were
// specific to collection1
for (String reporterName : RENAMED_REPORTERS) {
SolrMetricReporter reporter = reporters.get(reporterName);
assertNotNull("Reporter " + reporterName + " was not found.", reporter);
assertTrue(reporter instanceof MockMetricReporter);
MockMetricReporter mockReporter = (MockMetricReporter) reporter;
assertTrue("Reporter " + reporterName + " was not initialized: " + mockReporter, mockReporter.didInit);
assertTrue("Reporter " + reporterName + " was not validated: " + mockReporter, mockReporter.didValidate);
assertFalse("Reporter " + reporterName + " was incorrectly closed: " + mockReporter, mockReporter.didClose);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* 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;
import java.io.IOException;
import java.util.Locale;
import java.util.NoSuchElementException;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricReporter;
public class MockMetricReporter extends SolrMetricReporter {
public String configurable;
public boolean didInit = false;
public boolean didClose = false;
public boolean didValidate = false;
public MockMetricReporter(SolrMetricManager metricManager, String registryName) {
super(metricManager, registryName);
}
@Override
public void init(PluginInfo pluginInfo) {
super.init(pluginInfo);
didInit = true;
}
@Override
public void close() throws IOException {
didClose = true;
}
@Override
protected void validate() throws IllegalStateException {
didValidate = true;
if (configurable == null) {
throw new IllegalStateException("MockMetricReporter::configurable not defined.");
}
}
public void setConfigurable(String configurable) {
this.configurable = configurable;
}
public Metric reportMetric(String metricName) throws NoSuchElementException {
MetricRegistry registry = metricManager.registry(registryName);
Metric metric = registry.getMetrics().get(metricName);
if (metric == null) {
throw new NoSuchElementException("Metric was not found for metric name = " + metricName);
}
return metric;
}
@Override
public String toString() {
return String.format(Locale.ENGLISH, "[%s@%s: configurable = %s, didInit = %b, didValidate = %b, didClose = %b]",
getClass().getName(), Integer.toHexString(hashCode()), configurable, didInit, didValidate, didClose);
}
}

View File

@ -0,0 +1,156 @@
/*
* 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;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import com.codahale.metrics.Counter;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoMBean;
import org.apache.solr.metrics.SolrCoreMetricManager;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.metrics.SolrMetricReporter;
import org.apache.solr.metrics.SolrMetricTestUtils;
import org.apache.solr.schema.FieldType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class SolrJmxReporterTest extends SolrTestCaseJ4 {
private static final int MAX_ITERATIONS = 20;
private String domain;
private SolrCoreMetricManager coreMetricManager;
private SolrMetricManager metricManager;
private SolrJmxReporter reporter;
private MBeanServer mBeanServer;
private String reporterName;
@Before
public void beforeTest() throws Exception {
initCore("solrconfig-basic.xml", "schema.xml");
final SolrCore core = h.getCore();
domain = core.getName();
coreMetricManager = core.getCoreMetricManager();
metricManager = core.getCoreDescriptor().getCoreContainer().getMetricManager();
PluginInfo pluginInfo = createReporterPluginInfo();
metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
assertTrue("reporters.size should be > 0, but was + " + reporters.size(), reporters.size() > 0);
reporterName = pluginInfo.name;
assertNotNull("reporter " + reporterName + " not present among " + reporters, reporters.get(reporterName));
assertTrue("wrong reporter class: " + reporters.get(reporterName), reporters.get(reporterName) instanceof SolrJmxReporter);
reporter = (SolrJmxReporter) reporters.get(reporterName);
mBeanServer = reporter.getMBeanServer();
assertNotNull("MBean server not found.", mBeanServer);
}
private PluginInfo createReporterPluginInfo() {
Random random = random();
String className = SolrJmxReporter.class.getName();
String reporterName = TestUtil.randomSimpleString(random, 1, 10);
Map<String, Object> attrs = new HashMap<>();
attrs.put(FieldType.CLASS_NAME, className);
attrs.put(CoreAdminParams.NAME, reporterName);
boolean shouldOverrideDomain = random.nextBoolean();
if (shouldOverrideDomain) {
domain = TestUtil.randomSimpleString(random);
attrs.put("domain", domain);
}
return new PluginInfo(TestUtil.randomUnicodeString(random), attrs);
}
@After
public void afterTest() throws Exception {
metricManager.closeReporters(coreMetricManager.getRegistryName());
Set<ObjectInstance> objects =
mBeanServer.queryMBeans(ObjectName.getInstance(domain + ":*"), null);
assertTrue(objects.isEmpty());
coreMetricManager.close();
deleteCore();
}
@Test
public void testReportMetrics() throws Exception {
Random random = random();
Map<String, Counter> registered = new HashMap<>();
String scope = SolrMetricTestUtils.getRandomScope(random, true);
SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
for (int i = 0; i < iterations; ++i) {
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetricsWithReplacements(random, registered);
SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
coreMetricManager.registerMetricProducer(scope, producer);
registered.putAll(metrics);
//waitForListener();
Set<ObjectInstance> objects = mBeanServer.queryMBeans(null, null);
assertEquals(registered.size(), objects.stream().
filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
reporterName.equals(o.getObjectName().getKeyProperty("reporter"))).count());
}
}
@Test
public void testReloadCore() throws Exception {
Random random = random();
String scope = SolrMetricTestUtils.getRandomScope(random, true);
SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random, true);
SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
coreMetricManager.registerMetricProducer(scope, producer);
Set<ObjectInstance> objects = mBeanServer.queryMBeans(null, null);
assertEquals(metrics.size(), objects.stream().
filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
reporterName.equals(o.getObjectName().getKeyProperty("reporter"))).count());
h.getCoreContainer().reload(h.getCore().getName());
PluginInfo pluginInfo = createReporterPluginInfo();
metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
coreMetricManager.registerMetricProducer(scope, producer);
objects = mBeanServer.queryMBeans(null, null);
assertEquals(metrics.size(), objects.stream().
filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
pluginInfo.name.equals(o.getObjectName().getKeyProperty("reporter"))).count());
}
}

View File

@ -26,7 +26,7 @@ import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.junit.Test;
public class TimerUtilsTest extends SolrTestCaseJ4 {
public class MetricUtilsTest extends SolrTestCaseJ4 {
@Test
public void testSolrTimerGetSnapshot() {
@ -38,7 +38,7 @@ public class TimerUtilsTest extends SolrTestCaseJ4 {
}
// obtain timer metrics
final NamedList<Object> lst = new SimpleOrderedMap<>();
TimerUtils.addMetrics(lst, timer);
MetricUtils.addMetrics(lst, timer);
// check that expected metrics were obtained
assertEquals(lst.size(), 9);
final Snapshot snapshot = timer.getSnapshot();
@ -46,12 +46,12 @@ public class TimerUtilsTest extends SolrTestCaseJ4 {
// assertEquals(lst.get("avgRequestsPerSecond"), timer.getMeanRate());
assertEquals(lst.get("5minRateRequestsPerSecond"), timer.getFiveMinuteRate());
assertEquals(lst.get("15minRateRequestsPerSecond"), timer.getFifteenMinuteRate());
assertEquals(lst.get("avgTimePerRequest"), TimerUtils.nsToMs(snapshot.getMean()));
assertEquals(lst.get("medianRequestTime"), TimerUtils.nsToMs(snapshot.getMedian()));
assertEquals(lst.get("75thPcRequestTime"), TimerUtils.nsToMs(snapshot.get75thPercentile()));
assertEquals(lst.get("95thPcRequestTime"), TimerUtils.nsToMs(snapshot.get95thPercentile()));
assertEquals(lst.get("99thPcRequestTime"), TimerUtils.nsToMs(snapshot.get99thPercentile()));
assertEquals(lst.get("999thPcRequestTime"), TimerUtils.nsToMs(snapshot.get999thPercentile()));
assertEquals(lst.get("avgTimePerRequest"), MetricUtils.nsToMs(snapshot.getMean()));
assertEquals(lst.get("medianRequestTime"), MetricUtils.nsToMs(snapshot.getMedian()));
assertEquals(lst.get("75thPcRequestTime"), MetricUtils.nsToMs(snapshot.get75thPercentile()));
assertEquals(lst.get("95thPcRequestTime"), MetricUtils.nsToMs(snapshot.get95thPercentile()));
assertEquals(lst.get("99thPcRequestTime"), MetricUtils.nsToMs(snapshot.get99thPercentile()));
assertEquals(lst.get("999thPcRequestTime"), MetricUtils.nsToMs(snapshot.get999thPercentile()));
}
}

View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2010-2012 Coda Hale and Yammer, Inc.
Licensed 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.

View File

@ -0,0 +1,12 @@
Metrics
Copyright 2010-2013 Coda Hale and Yammer, Inc.
This product includes software developed by Coda Hale and Yammer, Inc.
This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
LongAdder), which was released with the following comments:
Written by Doug Lea with assistance from members of JCP JSR-166
Expert Group and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1 @@
7f2fe1039424ca687bea5d09ec0bfa372bf7d062

View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2010-2012 Coda Hale and Yammer, Inc.
Licensed 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.

View File

@ -0,0 +1,12 @@
Metrics
Copyright 2010-2013 Coda Hale and Yammer, Inc.
This product includes software developed by Coda Hale and Yammer, Inc.
This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
LongAdder), which was released with the following comments:
Written by Doug Lea with assistance from members of JCP JSR-166
Expert Group and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1 @@
ed364e77218e50fdcdebce4d982cb4d1f4a8c187

View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2010-2012 Coda Hale and Yammer, Inc.
Licensed 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.

View File

@ -0,0 +1,12 @@
Metrics
Copyright 2010-2013 Coda Hale and Yammer, Inc.
This product includes software developed by Coda Hale and Yammer, Inc.
This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
LongAdder), which was released with the following comments:
Written by Doug Lea with assistance from members of JCP JSR-166
Expert Group and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2010-2012 Coda Hale and Yammer, Inc.
Licensed 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.

View File

@ -0,0 +1,12 @@
Metrics
Copyright 2010-2013 Coda Hale and Yammer, Inc.
This product includes software developed by Coda Hale and Yammer, Inc.
This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
LongAdder), which was released with the following comments:
Written by Doug Lea with assistance from members of JCP JSR-166
Expert Group and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -39,8 +39,8 @@
<target name="resolve" depends="ivy-availability-check,ivy-fail,ivy-configure">
<sequential>
<!-- jetty libs in lib/ -->
<ivy:retrieve conf="jetty,servlet" type="jar" log="download-only" symlink="${ivy.symlink}"
pattern="lib/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
<ivy:retrieve conf="jetty,servlet,metrics" type="jar,bundle" log="download-only" symlink="${ivy.symlink}"
pattern="lib/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
<ivy:retrieve conf="logging" type="jar,bundle" log="download-only" symlink="${ivy.symlink}"
pattern="lib/ext/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
<!-- start.jar - we don't use sync=true here, we don't own the dir, but

View File

@ -29,9 +29,16 @@
<!-- Consult the javadoc of o.e.j.util.thread.QueuedThreadPool -->
<!-- for all configuration that may be set here. -->
<!-- =========================================================== -->
<!-- uncomment to change type of threadpool
<Arg name="threadpool"><New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"/></Arg>
-->
<Arg name="threadpool">
<New id="threadpool" class="com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool">
<Arg name="registry">
<Call id="solrJettyMetricRegistry" name="getOrCreate" class="com.codahale.metrics.SharedMetricRegistries">
<Arg>solr.jetty</Arg>
</Call>
</Arg>
</New>
</Arg>
<Get name="ThreadPool">
<Set name="minThreads" type="int"><Property name="solr.jetty.threads.min" default="10"/></Set>
<Set name="maxThreads" type="int"><Property name="solr.jetty.threads.max" default="10000"/></Set>
@ -106,7 +113,12 @@
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>
<Item>
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
<New id="InstrumentedHandler" class="com.codahale.metrics.jetty9.InstrumentedHandler">
<Arg><Ref refid="solrJettyMetricRegistry"/></Arg>
<Set name="handler">
<New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
</Set>
</New>
</Item>
<Item>
<New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>

View File

@ -18,7 +18,8 @@
-->
<ivy-module version="2.0">
<info organisation="org.apache.solr" module="server"/>
<configurations defaultconfmapping="jetty->master;start->master;servlet->master;logging->master">
<configurations defaultconfmapping="metrics->master;jetty->master;start->master;servlet->master;logging->master">
<conf name="metrics" description="metrics jars" transitive="true"/>
<conf name="jetty" description="jetty jars" transitive="false"/>
<conf name="start" description="jetty start jar" transitive="false"/>
<conf name="servlet" description="servlet-api jar" transitive="false"/>
@ -30,8 +31,12 @@
<dependency org="org.slf4j" name="slf4j-api" rev="${/org.slf4j/slf4j-api}" conf="logging"/>
<dependency org="org.slf4j" name="jcl-over-slf4j" rev="${/org.slf4j/jcl-over-slf4j}" conf="logging"/>
<dependency org="org.slf4j" name="jul-to-slf4j" rev="${/org.slf4j/jul-to-slf4j}" conf="logging"/>
<dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="logging"/>
<dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="logging"/>
<dependency org="io.dropwizard.metrics" name="metrics-core" rev="${/io.dropwizard.metrics/metrics-core}" conf="metrics" />
<dependency org="io.dropwizard.metrics" name="metrics-jetty9" rev="${/io.dropwizard.metrics/metrics-jetty9}" conf="metrics" />
<dependency org="io.dropwizard.metrics" name="metrics-jvm" rev="${/io.dropwizard.metrics/metrics-jvm}" conf="metrics" />
<dependency org="org.eclipse.jetty" name="jetty-continuation" rev="${/org.eclipse.jetty/jetty-continuation}" conf="jetty"/>
<dependency org="org.eclipse.jetty" name="jetty-deploy" rev="${/org.eclipse.jetty/jetty-deploy}" conf="jetty"/>
<dependency org="org.eclipse.jetty" name="jetty-http" rev="${/org.eclipse.jetty/jetty-http}" conf="jetty"/>

View File

@ -180,13 +180,15 @@ public interface CommonParams {
String AUTHZ_PATH = "/admin/authorization";
String AUTHC_PATH = "/admin/authentication";
String ZK_PATH = "/admin/zookeeper";
String METRICS_PATH = "/admin/metrics";
Set<String> ADMIN_PATHS = new HashSet<>(Arrays.asList(
CORES_HANDLER_PATH,
COLLECTIONS_HANDLER_PATH,
CONFIGSETS_HANDLER_PATH,
AUTHC_PATH,
AUTHZ_PATH));
AUTHZ_PATH,
METRICS_PATH));
/** valid values for: <code>echoParams</code> */
enum EchoParamStyle {

View File

@ -37,11 +37,13 @@ import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.CorePropertiesLocator;
import org.apache.solr.core.CoresLocator;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.core.SolrXmlConfig;
import org.apache.solr.handler.UpdateRequestHandler;
import org.apache.solr.metrics.reporters.SolrJmxReporter;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
@ -189,10 +191,17 @@ public class TestHarness extends BaseTestHarness {
= new UpdateShardHandlerConfig(UpdateShardHandlerConfig.DEFAULT_MAXUPDATECONNECTIONS,
UpdateShardHandlerConfig.DEFAULT_MAXUPDATECONNECTIONSPERHOST,
30000, 30000);
// universal default metric reporter
Map<String,String> attributes = new HashMap<>();
attributes.put("name", "default");
attributes.put("class", SolrJmxReporter.class.getName());
PluginInfo defaultPlugin = new PluginInfo("reporter", attributes, null, null);
return new NodeConfig.NodeConfigBuilder("testNode", loader)
.setUseSchemaCache(Boolean.getBoolean("shareSchema"))
.setCloudConfig(cloudConfig)
.setUpdateShardHandlerConfig(updateShardHandlerConfig)
.setMetricReporterPlugins(new PluginInfo[] {defaultPlugin})
.build();
}