Adds Solr metrics gathering to jmx-metrics (#204)

* Adds Solr metrics gathering to jmx-metrics

* Adds brackets to non standard Solr units

* Shortens description of a JMX Solr metric

* Adds minor tweaks to Solr JMX documentation
This commit is contained in:
Stefan Kurek 2022-01-22 15:23:35 -05:00 committed by GitHub
parent a1b43d9faa
commit 69b51e1ae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 445 additions and 6 deletions

View File

@ -67,13 +67,14 @@ capable of being specified via the `otel.jmx.target.system` property as a comma-
mutually exclusive with `otel.jmx.groovy.script`. The currently supported target systems are:
| `otel.jmx.target.system` |
| ------------------------ |
|--------------------------|
| [`jvm`](./docs/target-systems/jvm.md) |
| [`activemq`](./docs/target-systems/activemq.md)|
| [`activemq`](./docs/target-systems/activemq.md) |
| [`cassandra`](./docs/target-systems/cassandra.md) |
| [`kafka`](./docs/target-systems/kafka.md) |
| [`kafka-consumer`](./docs/target-systems/kafka-consumer.md) |
| [`kafka-producer`](./docs/target-systems/kafka-producer.md) |
| [`solr`](./docs/target-systems/solr.md) |
| [`tomcat`](./docs/target-systems/tomcat.md) |
### JMX Query Helpers

View File

@ -0,0 +1,84 @@
# Solr Metrics
The JMX Metric Gatherer provides built in Solr metric gathering capabilities.
Details about using JMX with Solr can be found here: https://solr.apache.org/guide/6_6/using-jmx-with-solr.html
## Metrics
### Core Metrics
* Name: `solr.document.count`
* Description: The total number of indexed documents.
* Unit: `{documents}`
* Labels: `core`
* Instrument Type: ObservableLongUpDownCounter
* Name: `solr.index.size`
* Description: The total index size.
* Unit: `by`
* Labels: `core`
* Instrument Type: ObservableLongUpDownCounter
* Name: `solr.request.count`
* Description: The number of queries made.
* Unit: `{queries}`
* Labels: `core`, `type`, `handler`
* Instrument Type: ObservableLongCounter
* Name: `solr.request.time.average`
* Description: The average time of a query, based on Solr's histogram configuration.
* Unit: `ms`
* Labels: `core`, `type`, `handler`
* Instrument Type: ObservableDoubleValue
* Name: `solr.request.error.count`
* Description: The number of queries resulting in an error.
* Unit: `{queries}`
* Labels: `core`, `type`, `handler`
* Instrument Type: ObservableLongCounter
* Name: `solr.request.timeout.count`
* Description: The number of queries resulting in a timeout.
* Unit: `{queries}`
* Labels: `core`, `type`, `handler`
* Instrument Type: ObservableLongCounter
* Name: `solr.cache.eviction.count`
* Description: The number of evictions from a cache.
* Unit: `{evictions}`
* Labels: `core`, `cache`
* Instrument Type: ObservableLongCounter
* Name: `solr.cache.hit.count`
* Description: The number of hits from a cache.
* Unit: `{hits}`
* Labels: `core`, `cache`
* Instrument Type: ObservableLongCounter
* Name: `solr.cache.insert.count`
* Description: The number of inserts from a cache.
* Unit: `{inserts}`
* Labels: `core`, `cache`
* Instrument Type: ObservableLongCounter
* Name: `solr.cache.lookup.count`
* Description: The number of lookups from a cache.
* Unit: `{lookups}`
* Labels: `core`, `cache`
* Instrument Type: ObservableLongCounter
* Name: `solr.cache.size`
* Description: The size of the cache occupied in memory.
* Unit: `by`
* Labels: `core`, `cache`
* Instrument Type: ObservableLongUpDownCounter

View File

@ -0,0 +1,252 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.jmxmetrics.target_systems;
import static org.assertj.core.api.Assertions.entry;
import io.opentelemetry.contrib.jmxmetrics.AbstractIntegrationTest;
import io.opentelemetry.proto.metrics.v1.Metric;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
class SolrIntegrationTest extends AbstractIntegrationTest {
SolrIntegrationTest() {
super(/* configFromStdin= */ false, "target-systems/solr.properties");
}
@Container
GenericContainer<?> solr =
new GenericContainer<>("solr:8.8.2")
.withNetwork(Network.SHARED)
.withEnv("LOCAL_JMX", "no")
.withEnv("ENABLE_REMOTE_JMX_OPTS", "true")
.withEnv("RMI_PORT", "9990")
.withCommand("solr-precreate", "gettingstarted")
.withNetworkAliases("solr")
.withExposedPorts(9990)
.withStartupTimeout(Duration.ofMinutes(2))
.waitingFor(Wait.forListeningPort());
@Test
void endToEnd() {
waitAndAssertMetrics(
metric ->
assertSumWithAttributes(
metric,
"solr.document.count",
"The total number of indexed documents.",
"{documents}",
attrs -> attrs.containsOnly(entry("core", "gettingstarted"))),
metric ->
assertSumWithAttributes(
metric,
"solr.index.size",
"The total index size.",
"by",
attrs -> attrs.containsOnly(entry("core", "gettingstarted"))),
metric ->
assertSolrRequestSumMetric(
metric, "solr.request.count", "The number of queries made.", "{queries}"),
metric ->
assertSolrRequestGaugeMetric(
metric,
"solr.request.time.average",
"The average time of a query, based on Solr's histogram configuration.",
"ms"),
metric ->
assertSolrRequestSumMetric(
metric,
"solr.request.error.count",
"The number of queries resulting in an error.",
"{queries}"),
metric ->
assertSolrRequestSumMetric(
metric,
"solr.request.timeout.count",
"The number of queries resulting in a timeout.",
"{queries}"),
metric ->
assertSolrCacheSumMetric(
metric,
"solr.cache.eviction.count",
"The number of evictions from a cache.",
"{evictions}"),
metric ->
assertSolrCacheSumMetric(
metric, "solr.cache.hit.count", "The number of hits for a cache.", "{hits}"),
metric ->
assertSolrCacheSumMetric(
metric,
"solr.cache.insert.count",
"The number of inserts to a cache.",
"{inserts}"),
metric ->
assertSolrCacheSumMetric(
metric,
"solr.cache.lookup.count",
"The number of lookups to a cache.",
"{lookups}"),
metric ->
assertSolrCacheSumMetric(
metric, "solr.cache.size", "The size of the cache occupied in memory.", "by"));
}
private void assertSolrRequestSumMetric(
Metric metric, String name, String description, String unit) {
assertSumWithAttributes(
metric,
name,
description,
unit,
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"), entry("handler", "/get"), entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/csv"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/query"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/graph"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "update"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/debug/dump"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/json"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/stream"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/export"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/json/docs"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"), entry("handler", "/sql"), entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/select"),
entry("type", "QUERY")));
}
private void assertSolrRequestGaugeMetric(
Metric metric, String name, String description, String unit) {
assertGaugeWithAttributes(
metric,
name,
description,
unit,
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"), entry("handler", "/get"), entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/csv"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/query"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/graph"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "update"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/debug/dump"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/json"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/stream"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/export"),
entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/update/json/docs"),
entry("type", "UPDATE")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"), entry("handler", "/sql"), entry("type", "QUERY")),
attrs ->
attrs.containsExactly(
entry("core", "gettingstarted"),
entry("handler", "/select"),
entry("type", "QUERY")));
}
private void assertSolrCacheSumMetric(
Metric metric, String name, String description, String unit) {
assertSumWithAttributes(
metric,
name,
description,
unit,
attrs ->
attrs.containsExactly(entry("core", "gettingstarted"), entry("cache", "searcher")));
}
}

View File

@ -0,0 +1,7 @@
otel.jmx.interval.milliseconds = 3000
otel.metrics.exporter = otlp
otel.jmx.service.url = service:jmx:rmi:///jndi/rmi://solr:9990/jmxrmi
otel.jmx.target.system = solr
# these will be overridden by cmd line
otel.exporter.otlp.endpoint = http://host.testcontainers.internal

View File

@ -34,7 +34,14 @@ class JmxConfig {
static final List<String> AVAILABLE_TARGET_SYSTEMS =
Arrays.asList(
"activemq", "cassandra", "jvm", "kafka", "kafka-consumer", "kafka-producer", "tomcat");
"activemq",
"cassandra",
"jvm",
"kafka",
"kafka-consumer",
"kafka-producer",
"solr",
"tomcat");
final String serviceUrl;
final String groovyScript;

View File

@ -0,0 +1,81 @@
/*
* Copyright The OpenTelemetry Authors
*
* 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.
*/
def beanSolrCoreSearcherNumDocs = otel.mbean("solr:dom1=core,dom2=*,category=SEARCHER,scope=searcher,name=numDocs")
otel.instrument(beanSolrCoreSearcherNumDocs, "solr.document.count", "The total number of indexed documents.", "{documents}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") }],
"Value", otel.&longUpDownCounterCallback)
def beanSolrCoreIndexSize = otel.mbean("solr:dom1=core,dom2=*,category=INDEX,name=sizeInBytes")
otel.instrument(beanSolrCoreIndexSize, "solr.index.size", "The total index size.", "by",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") }],
"Value", otel.&longUpDownCounterCallback)
def beanSolrCoreRequests = otel.mbeans(["solr:dom1=core,dom2=*,category=QUERY,scope=*,name=requests",
"solr:dom1=core,dom2=*,category=UPDATE,scope=*,name=requests"])
otel.instrument(beanSolrCoreRequests, "solr.request.count", "The number of queries made.", "{queries}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"type" : { mbean -> mbean.name().getKeyProperty("category") },
"handler" : { mbean -> mbean.name().getKeyProperty("scope") }],
"Count", otel.&longCounterCallback)
def beanSolrCoreRequestTimes = otel.mbeans(["solr:dom1=core,dom2=*,category=QUERY,scope=*,name=requestTimes",
"solr:dom1=core,dom2=*,category=UPDATE,scope=*,name=requestTimes"])
otel.instrument(beanSolrCoreRequestTimes, "solr.request.time.average",
"The average time of a query, based on Solr's histogram configuration.",
"ms",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"type" : { mbean -> mbean.name().getKeyProperty("category") },
"handler" : { mbean -> mbean.name().getKeyProperty("scope") }],
"Mean", otel.&doubleValueCallback)
def beanSolrCoreErrors = otel.mbeans(["solr:dom1=core,dom2=*,category=QUERY,scope=*,name=errors",
"solr:dom1=core,dom2=*,category=UPDATE,scope=*,name=errors"])
otel.instrument(beanSolrCoreErrors, "solr.request.error.count", "The number of queries resulting in an error.", "{queries}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"type" : { mbean -> mbean.name().getKeyProperty("category") },
"handler" : { mbean -> mbean.name().getKeyProperty("scope") }],
"Count", otel.&longCounterCallback)
def beanSolrCoreTimeouts = otel.mbeans(["solr:dom1=core,dom2=*,category=QUERY,scope=*,name=timeouts",
"solr:dom1=core,dom2=*,category=UPDATE,scope=*,name=timeouts"])
otel.instrument(beanSolrCoreTimeouts, "solr.request.timeout.count", "The number of queries resulting in a timeout.", "{queries}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"type" : { mbean -> mbean.name().getKeyProperty("category") },
"handler" : { mbean -> mbean.name().getKeyProperty("scope") }],
"Count", otel.&longCounterCallback)
def beanSolrCoreQueryResultsCache = otel.mbean("solr:dom1=core,dom2=*,category=CACHE,scope=*,name=queryResultCache")
otel.instrument(beanSolrCoreQueryResultsCache, "solr.cache.eviction.count", "The number of evictions from a cache.", "{evictions}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"cache" : { mbean -> mbean.name().getKeyProperty("scope") }],
"cumulative_evictions", otel.&longCounterCallback)
otel.instrument(beanSolrCoreQueryResultsCache, "solr.cache.hit.count", "The number of hits for a cache.", "{hits}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"cache" : { mbean -> mbean.name().getKeyProperty("scope") }],
"cumulative_hits", otel.&longCounterCallback)
otel.instrument(beanSolrCoreQueryResultsCache, "solr.cache.insert.count", "The number of inserts to a cache.", "{inserts}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"cache" : { mbean -> mbean.name().getKeyProperty("scope") }],
"cumulative_inserts", otel.&longCounterCallback)
otel.instrument(beanSolrCoreQueryResultsCache, "solr.cache.lookup.count", "The number of lookups to a cache.", "{lookups}",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"cache" : { mbean -> mbean.name().getKeyProperty("scope") }],
"cumulative_lookups", otel.&longCounterCallback)
otel.instrument(beanSolrCoreQueryResultsCache, "solr.cache.size", "The size of the cache occupied in memory.", "by",
["core" : { mbean -> mbean.name().getKeyProperty("dom2") },
"cache" : { mbean -> mbean.name().getKeyProperty("scope") }],
"size", otel.&longUpDownCounterCallback)

View File

@ -18,7 +18,14 @@ class JmxConfigTest {
void staticValues() {
assertThat(JmxConfig.AVAILABLE_TARGET_SYSTEMS)
.containsOnly(
"activemq", "cassandra", "jvm", "kafka", "kafka-consumer", "kafka-producer", "tomcat");
"activemq",
"cassandra",
"jvm",
"kafka",
"kafka-consumer",
"kafka-producer",
"solr",
"tomcat");
}
@Test
@ -115,8 +122,8 @@ class JmxConfigTest {
assertThatThrownBy(config::validate)
.isInstanceOf(ConfigurationException.class)
.hasMessage(
"[jvm, unavailabletargetsystem] must specify targets from "
+ "[activemq, cassandra, jvm, kafka, kafka-consumer, kafka-producer, tomcat]");
"[jvm, unavailabletargetsystem] must specify targets from [activemq, cassandra, jvm, "
+ "kafka, kafka-consumer, kafka-producer, solr, tomcat]");
}
@Test