You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2022/09/02 16:26:04 UTC
[solr] branch branch_9x updated: SOLR-15007: Aggregated node level metrics for request handlers (#948)
This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new 2e2473dc218 SOLR-15007: Aggregated node level metrics for request handlers (#948)
2e2473dc218 is described below
commit 2e2473dc2185af5731bc5a816a2e632d81ab7005
Author: Justin Sweeney <ju...@gmail.com>
AuthorDate: Fri Sep 2 00:58:07 2022 -0400
SOLR-15007: Aggregated node level metrics for request handlers (#948)
Useful when there are lots of cores.
Co-authored-by: Justin Sweeney <ju...@fullstory.com>
---
solr/CHANGES.txt | 2 +
.../apache/solr/handler/RequestHandlerBase.java | 19 +-
.../solr/metrics/DelegateRegistryCounter.java | 74 ++++++++
.../solr/metrics/DelegateRegistryHistogram.java | 69 +++++++
.../apache/solr/metrics/DelegateRegistryMeter.java | 82 ++++++++
.../apache/solr/metrics/DelegateRegistryTimer.java | 130 +++++++++++++
.../org/apache/solr/metrics/MetricSuppliers.java | 2 +-
.../SolrDelegateRegistryMetricsContext.java | 84 ++++++++
.../org/apache/solr/metrics/SolrMetricManager.java | 4 +
.../cloud-aggregate-node-metrics/conf/schema.xml | 29 +++
.../conf/solrconfig.xml | 55 ++++++
.../solr/handler/RequestHandlerMetricsTest.java | 174 +++++++++++++++++
.../solr/metrics/DelegateRegistryTimerTest.java | 211 +++++++++++++++++++++
.../deployment-guide/pages/metrics-reporting.adoc | 16 ++
14 files changed, 949 insertions(+), 2 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d961883fec3..2a3d76270fd 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -25,6 +25,8 @@ New Features
* SOLR-16282: CoreAdminHandler supports custom actions via solr.xml configuration. (Artem Abeleshev, Christine Poerschke)
+* SOLR-15007: Add ability to roll up core level metrics to be node level metrics for a RequestHandler via configuration. (Justin Sweeney, David Smiley)
+
Improvements
---------------------
* SOLR-15986: CommitUpdateCommand and SplitIndexCommand can write user commit metadata. (Bruno Roustant)
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index f2ed4de9ef4..d7268212ea2 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -35,7 +35,9 @@ import org.apache.solr.core.MetricsConfig;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoBean;
+import org.apache.solr.metrics.SolrDelegateRegistryMetricsContext;
import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
@@ -60,6 +62,7 @@ public abstract class RequestHandlerBase
protected SolrParams appends;
protected SolrParams invariants;
protected boolean httpCaching = true;
+ protected boolean aggregateNodeLevelMetricsEnabled = false;
protected SolrMetricsContext solrMetricsContext;
protected HandlerMetrics metrics = HandlerMetrics.NO_OP;
@@ -131,6 +134,11 @@ public abstract class RequestHandlerBase
if (initArgs != null) {
Object caching = initArgs.get("httpCaching");
httpCaching = caching != null ? Boolean.parseBoolean(caching.toString()) : true;
+ Boolean aggregateNodeLevelMetricsEnabled =
+ initArgs.getBooleanArg("aggregateNodeLevelMetricsEnabled");
+ if (aggregateNodeLevelMetricsEnabled != null) {
+ this.aggregateNodeLevelMetricsEnabled = aggregateNodeLevelMetricsEnabled;
+ }
}
}
@@ -141,7 +149,16 @@ public abstract class RequestHandlerBase
@Override
public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
- this.solrMetricsContext = parentContext.getChildContext(this);
+ if (aggregateNodeLevelMetricsEnabled) {
+ this.solrMetricsContext =
+ new SolrDelegateRegistryMetricsContext(
+ parentContext.getMetricManager(),
+ parentContext.getRegistryName(),
+ SolrMetricProducer.getUniqueMetricTag(this, parentContext.getTag()),
+ SolrMetricManager.getRegistryName(SolrInfoBean.Group.node));
+ } else {
+ this.solrMetricsContext = parentContext.getChildContext(this);
+ }
metrics = new HandlerMetrics(solrMetricsContext, getCategory().toString(), scope);
solrMetricsContext.gauge(
() -> handlerStart, true, "handlerStart", getCategory().toString(), scope);
diff --git a/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryCounter.java b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryCounter.java
new file mode 100644
index 00000000000..c8cc2352818
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryCounter.java
@@ -0,0 +1,74 @@
+/*
+ * 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.Counter;
+
+/**
+ * A counter implementation that is aware of both primary and delegate metrics belonging to
+ * different registries and able to update metrics in multiple registries
+ *
+ * @see SolrDelegateRegistryMetricsContext
+ */
+public class DelegateRegistryCounter extends Counter {
+
+ private final Counter primaryCounter;
+ private final Counter delegateCounter;
+
+ public DelegateRegistryCounter(Counter primaryCounter, Counter delegateCounter) {
+ this.primaryCounter = primaryCounter;
+ this.delegateCounter = delegateCounter;
+ }
+
+ @Override
+ public void inc() {
+ primaryCounter.inc();
+ delegateCounter.inc();
+ }
+
+ @Override
+ public void inc(long n) {
+ primaryCounter.inc(n);
+ delegateCounter.inc(n);
+ }
+
+ @Override
+ public void dec() {
+ primaryCounter.dec();
+ delegateCounter.dec();
+ }
+
+ @Override
+ public void dec(long n) {
+ primaryCounter.dec(n);
+ delegateCounter.dec(n);
+ }
+
+ @Override
+ public long getCount() {
+ return primaryCounter.getCount();
+ }
+
+ public Counter getPrimaryCounter() {
+ return primaryCounter;
+ }
+
+ public Counter getDelegateCounter() {
+ return delegateCounter;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryHistogram.java b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryHistogram.java
new file mode 100644
index 00000000000..900641244ce
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryHistogram.java
@@ -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 com.codahale.metrics.Histogram;
+import com.codahale.metrics.Snapshot;
+
+/**
+ * A meter implementation that is aware of both primary and delegate metrics belonging to different
+ * registries and able to update metrics in multiple registries
+ *
+ * @see SolrDelegateRegistryMetricsContext
+ */
+public class DelegateRegistryHistogram extends Histogram {
+
+ private final Histogram primaryHistogram;
+ private final Histogram delegateHistogram;
+
+ public DelegateRegistryHistogram(Histogram primaryHistogram, Histogram delegateHistogram) {
+ super(null);
+ this.primaryHistogram = primaryHistogram;
+ this.delegateHistogram = delegateHistogram;
+ }
+
+ @Override
+ public void update(int value) {
+ primaryHistogram.update(value);
+ delegateHistogram.update(value);
+ }
+
+ @Override
+ public void update(long value) {
+ primaryHistogram.update(value);
+ delegateHistogram.update(value);
+ }
+
+ @Override
+ public long getCount() {
+ return primaryHistogram.getCount();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return primaryHistogram.getSnapshot();
+ }
+
+ public Histogram getPrimaryHistogram() {
+ return primaryHistogram;
+ }
+
+ public Histogram getDelegateHistogram() {
+ return delegateHistogram;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryMeter.java b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryMeter.java
new file mode 100644
index 00000000000..20903cc7f12
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryMeter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Meter;
+
+/**
+ * A meter implementation that is aware of both primary and delegate metrics belonging to different
+ * registries and able to update metrics in multiple registries
+ *
+ * @see SolrDelegateRegistryMetricsContext
+ */
+public class DelegateRegistryMeter extends Meter {
+
+ private final Meter primaryMeter;
+ private final Meter delegateMeter;
+
+ public DelegateRegistryMeter(Meter primaryMeter, Meter delegateMeter) {
+ this.primaryMeter = primaryMeter;
+ this.delegateMeter = delegateMeter;
+ }
+
+ @Override
+ public void mark() {
+ primaryMeter.mark();
+ delegateMeter.mark();
+ }
+
+ @Override
+ public void mark(long n) {
+ primaryMeter.mark(n);
+ delegateMeter.mark(n);
+ }
+
+ @Override
+ public long getCount() {
+ return primaryMeter.getCount();
+ }
+
+ @Override
+ public double getFifteenMinuteRate() {
+ return primaryMeter.getFifteenMinuteRate();
+ }
+
+ @Override
+ public double getFiveMinuteRate() {
+ return primaryMeter.getFiveMinuteRate();
+ }
+
+ @Override
+ public double getMeanRate() {
+ return primaryMeter.getMeanRate();
+ }
+
+ @Override
+ public double getOneMinuteRate() {
+ return primaryMeter.getOneMinuteRate();
+ }
+
+ public Meter getPrimaryMeter() {
+ return primaryMeter;
+ }
+
+ public Meter getDelegateMeter() {
+ return delegateMeter;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryTimer.java b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryTimer.java
new file mode 100644
index 00000000000..3519ee55cef
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/DelegateRegistryTimer.java
@@ -0,0 +1,130 @@
+/*
+ * 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.Clock;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import java.time.Duration;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * A timer implementation that is aware of both primary and delegate metrics belonging to different
+ * registries and able to update metrics in multiple registries
+ *
+ * @see SolrDelegateRegistryMetricsContext
+ */
+public class DelegateRegistryTimer extends Timer {
+
+ private final Timer primaryTimer;
+ private final Timer delegateTimer;
+ private final Clock clock;
+
+ public DelegateRegistryTimer(Clock clock, Timer primaryTimer, Timer delegateTimer) {
+ this.primaryTimer = primaryTimer;
+ this.delegateTimer = delegateTimer;
+ this.clock = clock;
+ }
+
+ @Override
+ public void update(long duration, TimeUnit unit) {
+ primaryTimer.update(duration, unit);
+ delegateTimer.update(duration, unit);
+ }
+
+ @Override
+ public void update(Duration duration) {
+ primaryTimer.update(duration);
+ delegateTimer.update(duration);
+ }
+
+ @Override
+ public <T> T time(Callable<T> event) throws Exception {
+ final long startTime = clock.getTick();
+ try {
+ return event.call();
+ } finally {
+ update(clock.getTick() - startTime, TimeUnit.NANOSECONDS);
+ }
+ }
+
+ @Override
+ public <T> T timeSupplier(Supplier<T> event) {
+ final long startTime = clock.getTick();
+ try {
+ return event.get();
+ } finally {
+ update(clock.getTick() - startTime, TimeUnit.NANOSECONDS);
+ }
+ }
+
+ @Override
+ public void time(Runnable event) {
+ final long startTime = clock.getTick();
+ try {
+ event.run();
+ } finally {
+ update(clock.getTick() - startTime, TimeUnit.NANOSECONDS);
+ }
+ }
+
+ @Override
+ public Context time() {
+ return super.time();
+ }
+
+ @Override
+ public long getCount() {
+ return primaryTimer.getCount();
+ }
+
+ @Override
+ public double getFifteenMinuteRate() {
+ return primaryTimer.getFifteenMinuteRate();
+ }
+
+ @Override
+ public double getFiveMinuteRate() {
+ return primaryTimer.getFiveMinuteRate();
+ }
+
+ @Override
+ public double getMeanRate() {
+ return primaryTimer.getMeanRate();
+ }
+
+ @Override
+ public double getOneMinuteRate() {
+ return primaryTimer.getOneMinuteRate();
+ }
+
+ @Override
+ public Snapshot getSnapshot() {
+ return primaryTimer.getSnapshot();
+ }
+
+ public Timer getPrimaryTimer() {
+ return primaryTimer;
+ }
+
+ public Timer getDelegateTimer() {
+ return delegateTimer;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java b/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
index f303bef6960..497be19d7f1 100644
--- a/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
+++ b/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
@@ -112,7 +112,7 @@ public class MetricSuppliers {
}
}
- private static Clock getClock(PluginInfo info, String param) {
+ public static Clock getClock(PluginInfo info, String param) {
if (info == null) {
return Clock.defaultClock();
}
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrDelegateRegistryMetricsContext.java b/solr/core/src/java/org/apache/solr/metrics/SolrDelegateRegistryMetricsContext.java
new file mode 100644
index 00000000000..bfab5f40b92
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrDelegateRegistryMetricsContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Timer;
+
+/**
+ * This class represents a metrics context that is delegate aware in that it is aware of multiple
+ * metric registries, a primary and a delegate. This enables creating metrics that are tracked at
+ * multiple levels, i.e. core-level and node-level. This class will create instances of new Timer,
+ * Meter, Counter, Histogram implementations that hold references to both primary and delegate
+ * implementations of corresponding classes. The DelegateRegistry* metric classes are just
+ * pass-through to two different implementations. As such the DelegateRegistry* metric classes do
+ * not hold any metric data themselves.
+ *
+ * @see org.apache.solr.metrics.SolrMetricsContext
+ */
+public class SolrDelegateRegistryMetricsContext extends SolrMetricsContext {
+
+ private final String delegateRegistry;
+
+ public SolrDelegateRegistryMetricsContext(
+ SolrMetricManager metricManager, String registry, String tag, String delegateRegistry) {
+ super(metricManager, registry, tag);
+ this.delegateRegistry = delegateRegistry;
+ }
+
+ @Override
+ public Meter meter(String metricName, String... metricPath) {
+ return new DelegateRegistryMeter(
+ super.meter(metricName, metricPath),
+ getMetricManager().meter(this, delegateRegistry, metricName, metricPath));
+ }
+
+ @Override
+ public Counter counter(String metricName, String... metricPath) {
+ return new DelegateRegistryCounter(
+ super.counter(metricName, metricPath),
+ getMetricManager().counter(this, delegateRegistry, metricName, metricPath));
+ }
+
+ @Override
+ public Timer timer(String metricName, String... metricPath) {
+ return new DelegateRegistryTimer(
+ MetricSuppliers.getClock(
+ getMetricManager().getMetricsConfig().getTimerSupplier(), MetricSuppliers.CLOCK),
+ super.timer(metricName, metricPath),
+ getMetricManager().timer(this, delegateRegistry, metricName, metricPath));
+ }
+
+ @Override
+ public Histogram histogram(String metricName, String... metricPath) {
+ return new DelegateRegistryHistogram(
+ super.histogram(metricName, metricPath),
+ getMetricManager().histogram(this, delegateRegistry, metricName, metricPath));
+ }
+
+ @Override
+ public SolrMetricsContext getChildContext(Object child) {
+ return new SolrDelegateRegistryMetricsContext(
+ getMetricManager(),
+ getRegistryName(),
+ SolrMetricProducer.getUniqueMetricTag(child, getTag()),
+ delegateRegistry);
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index 022de145224..bd63e9e8a22 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -1367,4 +1367,8 @@ public class SolrMetricManager {
}
}
}
+
+ public MetricsConfig getMetricsConfig() {
+ return metricsConfig;
+ }
}
diff --git a/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/schema.xml
new file mode 100644
index 00000000000..4124feab0c3
--- /dev/null
+++ b/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/schema.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<schema name="minimal" version="1.1">
+ <fieldType name="string" class="solr.StrField"/>
+ <fieldType name="int" class="${solr.tests.IntegerFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+ <fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
+ <dynamicField name="*" type="string" indexed="true" stored="true"/>
+ <!-- for versioning -->
+ <field name="_version_" type="long" indexed="true" stored="true"/>
+ <field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
+ <field name="id" type="string" indexed="true" stored="true"/>
+ <dynamicField name="*_s" type="string" indexed="true" stored="true" />
+ <uniqueKey>id</uniqueKey>
+</schema>
diff --git a/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/solrconfig.xml b/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/solrconfig.xml
new file mode 100644
index 00000000000..f23456d060a
--- /dev/null
+++ b/solr/core/src/test-files/solr/configsets/cloud-aggregate-node-metrics/conf/solrconfig.xml
@@ -0,0 +1,55 @@
+<?xml version="1.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.
+-->
+
+<!-- Minimal solrconfig.xml with /select, /admin and /update only -->
+
+<config>
+
+ <dataDir>${solr.data.dir:}</dataDir>
+
+ <directoryFactory name="DirectoryFactory"
+ class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+ <schemaFactory class="ClassicIndexSchemaFactory"/>
+
+ <luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>
+
+ <updateHandler class="solr.DirectUpdateHandler2">
+ <commitWithin>
+ <softCommit>${solr.commitwithin.softcommit:true}</softCommit>
+ </commitWithin>
+ <updateLog class="${solr.ulog:solr.UpdateLog}"></updateLog>
+ </updateHandler>
+
+ <requestHandler name="/update" class="solr.UpdateRequestHandler">
+ <str name="useParams">_UPDATE</str>
+ <bool name="aggregateNodeLevelMetricsEnabled">true</bool>
+ </requestHandler>
+
+ <requestHandler name="/select" class="solr.SearchHandler">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <str name="indent">true</str>
+ <str name="df">text</str>
+ </lst>
+ <bool name="aggregateNodeLevelMetricsEnabled">true</bool>
+ </requestHandler>
+ <indexConfig>
+ <mergeScheduler class="${solr.mscheduler:org.apache.lucene.index.ConcurrentMergeScheduler}"/>
+ </indexConfig>
+</config>
\ No newline at end of file
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
new file mode 100644
index 00000000000..228ad8cc48b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.util.NamedList;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class RequestHandlerMetricsTest extends SolrCloudTestCase {
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ System.setProperty("metricsEnabled", "true");
+ configureCluster(1).addConfig("conf1", configset("cloud-aggregate-node-metrics")).configure();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ cluster.deleteAllCollections();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.clearProperty("metricsEnabled");
+ }
+
+ @Test
+ @SuppressWarnings({"unchecked"})
+ public void testAggregateNodeLevelMetrics() throws SolrServerException, IOException {
+ String collection1 = "testRequestHandlerMetrics1";
+ String collection2 = "testRequestHandlerMetrics2";
+
+ CloudSolrClient cloudClient = cluster.getSolrClient();
+
+ CollectionAdminRequest.Create create =
+ CollectionAdminRequest.createCollection(collection1, "conf1", 1, 1);
+ cloudClient.request(create);
+ cluster.waitForActiveCollection(collection1, 1, 1);
+
+ create = CollectionAdminRequest.createCollection(collection2, "conf1", 1, 1);
+ cloudClient.request(create);
+ cluster.waitForActiveCollection(collection2, 1, 1);
+
+ SolrInputDocument solrInputDocument =
+ new SolrInputDocument("id", "10", "title", "test", "val_s1", "aaa");
+ cloudClient.add(collection1, solrInputDocument);
+ cloudClient.add(collection2, solrInputDocument);
+
+ SolrQuery solrQuery = new SolrQuery("*:*");
+ cloudClient.query(collection1, solrQuery);
+ cloudClient.query(collection2, solrQuery);
+
+ NamedList<Object> response =
+ cloudClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics", null));
+
+ NamedList<Object> metrics = (NamedList<Object>) response.get("metrics");
+
+ final double[] minQueryTime = {Double.MAX_VALUE};
+ final double[] maxQueryTime = {-1.0};
+ final double[] minUpdateTime = {Double.MAX_VALUE};
+ final double[] maxUpdateTime = {-1.0};
+ Set<NamedList<Object>> coreMetrics = new HashSet<>();
+ metrics.forEachKey(
+ (key) -> {
+ if (key.startsWith("solr.core.testRequestHandlerMetrics")) {
+ NamedList<Object> coreMetric = (NamedList<Object>) metrics.get(key);
+ coreMetrics.add(coreMetric);
+ }
+ });
+ assertEquals(2, coreMetrics.size());
+ coreMetrics.forEach(
+ metric -> {
+ assertEquals(
+ 1L,
+ ((Map<String, Number>) metric.get("QUERY./select.requestTimes"))
+ .get("count")
+ .longValue());
+ minQueryTime[0] =
+ Math.min(
+ minQueryTime[0],
+ ((Map<String, Number>) metric.get("QUERY./select.requestTimes"))
+ .get("min_ms")
+ .doubleValue());
+ maxQueryTime[0] =
+ Math.max(
+ maxQueryTime[0],
+ ((Map<String, Number>) metric.get("QUERY./select.requestTimes"))
+ .get("max_ms")
+ .doubleValue());
+ assertEquals(
+ 1L,
+ ((Map<String, Number>) metric.get("UPDATE./update.requestTimes"))
+ .get("count")
+ .longValue());
+ minUpdateTime[0] =
+ Math.min(
+ minUpdateTime[0],
+ ((Map<String, Number>) metric.get("UPDATE./update.requestTimes"))
+ .get("min_ms")
+ .doubleValue());
+ maxUpdateTime[0] =
+ Math.max(
+ maxUpdateTime[0],
+ ((Map<String, Number>) metric.get("UPDATE./update.requestTimes"))
+ .get("max_ms")
+ .doubleValue());
+ });
+
+ NamedList<Object> nodeMetrics = (NamedList<Object>) metrics.get("solr.node");
+ assertEquals(
+ 2L,
+ ((Map<String, Number>) nodeMetrics.get("QUERY./select.requestTimes"))
+ .get("count")
+ .longValue());
+ assertEquals(
+ minQueryTime[0],
+ ((Map<String, Number>) nodeMetrics.get("QUERY./select.requestTimes"))
+ .get("min_ms")
+ .doubleValue(),
+ 0.0);
+ assertEquals(
+ maxQueryTime[0],
+ ((Map<String, Number>) nodeMetrics.get("QUERY./select.requestTimes"))
+ .get("max_ms")
+ .doubleValue(),
+ 0.0);
+ assertEquals(
+ 2L,
+ ((Map<String, Number>) nodeMetrics.get("UPDATE./update.requestTimes"))
+ .get("count")
+ .longValue());
+ assertEquals(
+ minUpdateTime[0],
+ ((Map<String, Number>) nodeMetrics.get("UPDATE./update.requestTimes"))
+ .get("min_ms")
+ .doubleValue(),
+ 0.0);
+ assertEquals(
+ maxUpdateTime[0],
+ ((Map<String, Number>) nodeMetrics.get("UPDATE./update.requestTimes"))
+ .get("max_ms")
+ .doubleValue(),
+ 0.0);
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/metrics/DelegateRegistryTimerTest.java b/solr/core/src/test/org/apache/solr/metrics/DelegateRegistryTimerTest.java
new file mode 100644
index 00000000000..4a6f44453e6
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/DelegateRegistryTimerTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.Test;
+
+public class DelegateRegistryTimerTest {
+
+ MetricRegistry.MetricSupplier<Timer> timerSupplier =
+ new MetricSuppliers.DefaultTimerSupplier(null);
+
+ @Test
+ public void update() {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ delegateRegistryTimer.update(Duration.ofNanos(100));
+ assertEquals(1, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(100.0, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(100.0, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMax());
+ assertEquals(100.0, delegateRegistryTimer.getSnapshot().getMean(), 0.0);
+ assertEquals(100.0, delegateRegistryTimer.getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getSnapshot().getMax());
+ assertEquals(1, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertEquals(100.0, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(100.0, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMax());
+
+ delegateRegistryTimer.update(Duration.ofNanos(200));
+ delegateRegistryTimer.update(Duration.ofNanos(300));
+ assertEquals(3, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(200.0, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(200.0, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMax());
+ assertEquals(200.0, delegateRegistryTimer.getSnapshot().getMean(), 0.0);
+ assertEquals(200.0, delegateRegistryTimer.getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getSnapshot().getMax());
+ assertEquals(3, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertEquals(200.0, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(200.0, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMax());
+ }
+
+ @Test
+ public void testUpdate() {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ delegateRegistryTimer.update(100, TimeUnit.NANOSECONDS);
+ assertEquals(1, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(100, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(100, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMax());
+ assertEquals(100, delegateRegistryTimer.getSnapshot().getMean(), 0.0);
+ assertEquals(100, delegateRegistryTimer.getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getSnapshot().getMax());
+ assertEquals(1, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertEquals(100, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(100, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(100L, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMax());
+
+ delegateRegistryTimer.update(200, TimeUnit.NANOSECONDS);
+ delegateRegistryTimer.update(300, TimeUnit.NANOSECONDS);
+ assertEquals(3, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(200, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(200, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMax());
+ assertEquals(200, delegateRegistryTimer.getSnapshot().getMean(), 0.0);
+ assertEquals(200, delegateRegistryTimer.getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getSnapshot().getMax());
+ assertEquals(3, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertEquals(200, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(), 0.0);
+ assertEquals(200, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMedian(), 0.0);
+ assertEquals(300L, delegateRegistryTimer.getDelegateTimer().getSnapshot().getMax());
+ }
+
+ @Test
+ public void timeContext() throws InterruptedException {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ Timer.Context time = delegateRegistryTimer.time();
+ Thread.sleep(100);
+ time.close();
+ assertTrue(delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean() > 100000);
+ assertTrue(delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean() > 100000);
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(),
+ 0.0);
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getSnapshot().getMean(),
+ 0.0);
+ }
+
+ @Test
+ public void timeSupplier() {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ AtomicLong timeTaken = new AtomicLong();
+ Long supplierResult =
+ delegateRegistryTimer.timeSupplier(
+ () -> {
+ timeTaken.getAndSet(System.nanoTime());
+ for (int i = 0; i < 100; i++) {
+ // Just loop
+ }
+ timeTaken.getAndSet(System.nanoTime() - timeTaken.get());
+ return 1L;
+ });
+ assertEquals(Long.valueOf(1L), supplierResult);
+ assertEquals(1, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(1, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertTrue(delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean() > timeTaken.get());
+ assertTrue(delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean() > timeTaken.get());
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(),
+ 0.0);
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getSnapshot().getMean(),
+ 0.0);
+ }
+
+ @Test
+ public void testTimeCallable() throws Exception {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ AtomicLong timeTaken = new AtomicLong();
+ Long callableResult =
+ delegateRegistryTimer.time(
+ () -> {
+ timeTaken.getAndSet(System.nanoTime());
+ for (int i = 0; i < 100; i++) {
+ // Just loop
+ }
+ timeTaken.getAndSet(System.nanoTime() - timeTaken.get());
+ return 1L;
+ });
+ assertEquals(Long.valueOf(1L), callableResult);
+ assertEquals(1, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(1, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertTrue(delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean() > timeTaken.get());
+ assertTrue(delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean() > timeTaken.get());
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(),
+ 0.0);
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getSnapshot().getMean(),
+ 0.0);
+ }
+
+ @Test
+ public void testTimeRunnable() {
+ DelegateRegistryTimer delegateRegistryTimer =
+ new DelegateRegistryTimer(
+ Clock.defaultClock(), timerSupplier.newMetric(), timerSupplier.newMetric());
+ AtomicLong timeTaken = new AtomicLong();
+ delegateRegistryTimer.time(
+ () -> {
+ timeTaken.getAndSet(System.nanoTime());
+ for (int i = 0; i < 100; i++) {
+ // Just loop
+ }
+ timeTaken.getAndSet(System.nanoTime() - timeTaken.get());
+ });
+ assertEquals(1, delegateRegistryTimer.getPrimaryTimer().getCount());
+ assertEquals(1, delegateRegistryTimer.getDelegateTimer().getCount());
+ assertTrue(delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean() > timeTaken.get());
+ assertTrue(delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean() > timeTaken.get());
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getDelegateTimer().getSnapshot().getMean(),
+ 0.0);
+ assertEquals(
+ delegateRegistryTimer.getPrimaryTimer().getSnapshot().getMean(),
+ delegateRegistryTimer.getSnapshot().getMean(),
+ 0.0);
+ }
+}
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc
index 74b2cd7cf87..b97a25280cf 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/metrics-reporting.adoc
@@ -91,6 +91,22 @@ Handlers that support process distributed shard requests also report `shardReque
* shard replication and transaction log replay on replicas,
* open / available / pending connections for shard handler and update handler.
+RequestHandlers can be configured to roll up core level metrics to the node level in addition to reporting them per core. This is useful if you have a large number of cores per node and are interested in aggregate metrics per node. This is configured by adding `<bool name="aggregateNodeLevelMetricsEnabled">true</bool>` to a xref:configuration-guide:requesthandlers-searchcomponents.adoc#configuring-request-handlers[RequestHandler configuration] in your solrconfig.xml, for example:
+
+```
+<requestHandler name="/select" class="solr.SearchHandler">
+ <!-- default values for query parameters can be specified, these
+ will be overridden by parameters in the request
+ -->
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <int name="rows">10</int>
+ </lst>
+
+ <bool name="aggregateNodeLevelMetricsEnabled">true</bool>
+</requestHandler>
+```
+
=== Jetty Registry
This registry is returned at `solr.jetty` and includes the following information.