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:
parent
a1b43d9faa
commit
69b51e1ae9
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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")));
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue