Skip to content

Commit

Permalink
Add default JVM Metrics using new helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Fitzpatrick committed Oct 22, 2020
1 parent 3f22f82 commit 2ef6504
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 40 deletions.
1 change: 1 addition & 0 deletions contrib/jmx-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ The currently available target systems are:

| `otel.jmx.target.system` |
| ------------------------ |
| [`jvm`](./docs/target-systems/jvm.md) |
| [`cassandra`](./docs/target-systems/cassandra.md) |

### Configuration
Expand Down
88 changes: 88 additions & 0 deletions contrib/jmx-metrics/docs/target-systems/jvm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# JVM Metrics

The JMX Metric Gatherer provides built in JVM metric gathering capabilities.
These metrics are sourced from Cassandra's exposed Dropwizard Metrics for each node: https://cassandra.apache.org/doc/latest/operating/metrics.html.

## Metrics

### Client Request Metrics

* Name: `jvm.classes.loaded`
* Description: The number of loaded classes
* Unit: `1`
* Instrument Type: LongUpDownCounter

* Name: `jvm.gc.collections.count`
* Description: The total number of garbage collections that have occurred
* Unit: `1`
* Instrument Type: LongCounter

* Name: `jvm.gc.collections.elapsed`
* Description: The approximate accumulated collection elapsed time
* Unit: `ms`
* Instrument Type: LongCounter

* Name: `jvm.memory.heap.init`
* Description: The initial amount of memory that the JVM requests from the operating system for the heap
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.heap.max`
* Description: The maximum amount of memory can be used for the heap
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.heap.used`
* Description: The current heap memory usage
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.heap.committed`
* Description: The amount of memory that is guaranteed to be available for the heap
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.nonheap.init`
* Description: The initial amount of memory that the JVM requests from the operating system for non-heap purposes
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.nonheap.max`
* Description: The maximum amount of memory can be used for non-heap purposes
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.nonheap.used`
* Description: The current non-heap memory usage
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.nonheap.committed`
* Description: The amount of memory that is guaranteed to be available for non-heap purposes
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.pool.init`
* Description: The initial amount of memory that the JVM requests from the operating system for the memory pool
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.pool.max`
* Description: The maximum amount of memory can be used for the memory pool
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.pool.used`
* Description: The current memory pool memory usage
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.memory.pool.committed`
* Description: The amount of memory that is guaranteed to be available for the memory pool
* Unit: `by`
* Instrument Type: LongUpDownCounter

* Name: `jvm.threads.count`
* Description: The current number of threads
* Unit: `1`
* Instrument Type: LongUpDownCounter
30 changes: 25 additions & 5 deletions contrib/jmx-metrics/src/main/resources/target-systems/jvm.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,30 @@
* limitations under the License.
*/

package io.opentelemetry.contrib.jmxmetrics
def classLoading = otel.mbean("java.lang:type=ClassLoading")
otel.instrument(classLoading, "jvm.classes.loaded", "number of loaded classes",
"1", "LoadedClassCount", otel.&longUpDownCounter)

// This is a placeholder for default metric functionality
// per https://github.com/open-telemetry/opentelemetry-java-contrib/issues/12
def garbageCollector = otel.mbeans("java.lang:type=GarbageCollector,*")
otel.instrument(garbageCollector, "jvm.gc.collections.count", "total number of collections that have occurred",
"1", ["name" : { mbean -> mbean.name().getKeyProperty("name") }],
"CollectionCount", otel.&longCounter)
otel.instrument(garbageCollector, "jvm.gc.collections.elapsed",
"the approximate accumulated collection elapsed time in milliseconds", "ms",
["name" : { mbean -> mbean.name().getKeyProperty("name") }],
"CollectionTime", otel.&longCounter)

def counter = otel.longCounter("placeholder.metric", "For testing purposes")
counter.add(1)
def memory = otel.mbean("java.lang:type=Memory")
otel.instrument(memory, "jvm.memory.heap", "current heap usage",
"by", "HeapMemoryUsage", otel.&longUpDownCounter)
otel.instrument(memory, "jvm.memory.nonheap", "current non-heap usage",
"by", "NonHeapMemoryUsage", otel.&longUpDownCounter)

def memoryPool = otel.mbeans("java.lang:type=MemoryPool,*")
otel.instrument(memoryPool, "jvm.memory.pool", "current memory pool usage",
"by", ["name" : { mbean -> mbean.name().getKeyProperty("name") }],
"Usage", otel.&longUpDownCounter)

def threading = otel.mbean("java.lang:type=Threading")
otel.instrument(threading, "jvm.threads.count", "number of threads",
"1", "ThreadCount", otel.&longUpDownCounter)
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ class MBeanHelperTest extends Specification {
then: "${quantity} returned"
def returned = mbeanHelper.getAttribute("SomeAttribute")
println "MBeanHelperTest.represents #quantity MBean(s): ${returned}"
returned == isSingle ? ["0"]: (0..100).collect {it as String}.sort()
where:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class CassandraIntegrationTests extends OtlpIntegrationTest {
metrics.sort{ a, b -> a.name <=> b.name}
then: 'they are of the expected size and content'
metrics.size() == 23
println "CassandraIntegrationTests.end to end: ${metrics}"

def expectedMetrics = [
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,207 @@

package io.opentelemetry.contrib.jmxmetrics

import org.apache.hc.client5.http.fluent.Request
import io.opentelemetry.proto.metrics.v1.IntSum

import io.opentelemetry.proto.common.v1.InstrumentationLibrary
import io.opentelemetry.proto.common.v1.StringKeyValue
import io.opentelemetry.proto.metrics.v1.InstrumentationLibraryMetrics
import io.opentelemetry.proto.metrics.v1.Metric
import io.opentelemetry.proto.metrics.v1.ResourceMetrics
import org.testcontainers.Testcontainers
import spock.lang.Requires
import spock.lang.Timeout

@Requires({
System.getProperty('ojc.integration.tests') == 'true'
})
@Timeout(60)
class JVMTargetSystemIntegrationTests extends IntegrationTest {

def receivedMetrics() {
def scraped = []
for (int i = 0; i < 120; i++) {
def resp = Request.get("http://localhost:${jmxExposedPort}/metrics").execute()
def received = resp.returnContent().asString()
if (received != '') {
scraped = received.split('\n')
if (scraped.size() > 2) {
break
}
}
Thread.sleep(500)
}
return scraped
}
class JVMTargetSystemIntegrationTests extends OtlpIntegrationTest {

def 'end to end'() {
setup: 'we configure JMX metrics gatherer and target server to use default JVM target system script'
configureContainers('jvm_config.properties', 0, 9123, false)
setup: 'we configure JMX metrics gatherer and target server to use JVM as target system'
Testcontainers.exposeHostPorts(otlpPort)
configureContainers('target-systems/jvm.properties', otlpPort, 0, false)

expect:
when: 'we receive metrics from the prometheus endpoint'
def scraped = receivedMetrics()
when: 'we receive metrics from the JMX metrics gatherer'
List<ResourceMetrics> receivedMetrics = collector.receivedMetrics
then: 'they are of the expected size'
receivedMetrics.size() == 1

when: "we examine the received metric's instrumentation library metrics lists"
ResourceMetrics receivedMetric = receivedMetrics.get(0)
List<InstrumentationLibraryMetrics> ilMetrics =
receivedMetric.instrumentationLibraryMetricsList
then: 'they of the expected size'
ilMetrics.size() == 1

when: 'we examine the instrumentation library'
InstrumentationLibraryMetrics ilMetric = ilMetrics.get(0)
InstrumentationLibrary il = ilMetric.instrumentationLibrary
then: 'it is of the expected content'
il.name == 'io.opentelemetry.contrib.jmxmetrics'
il.version == '0.0.1'

then: 'they are of the expected format'
scraped.size() == 3
scraped[0].contains(
'# HELP placeholder_metric For testing purposes')
scraped[1].contains(
'# TYPE placeholder_metric counter')
scraped[2].contains(
'placeholder_metric 1.0')
when: 'we examine the instrumentation library metric metrics list'
ArrayList<Metric> metrics = ilMetric.metricsList as ArrayList
metrics.sort{ a, b -> a.name <=> b.name}
then: 'they are of the expected size and content'
metrics.size() == 16

def expectedMetrics = [
[
'jvm.classes.loaded',
'number of loaded classes',
'1',
[]
],
[
'jvm.gc.collections.count',
'total number of collections that have occurred',
'1',
[
"ConcurrentMarkSweep",
"ParNew"]
],
[
'jvm.gc.collections.elapsed',
'the approximate accumulated collection elapsed time in milliseconds',
'ms',
[
"ConcurrentMarkSweep",
"ParNew"]
],
[
'jvm.memory.heap.committed',
'current heap usage',
'by',
[]
],
[
'jvm.memory.heap.init',
'current heap usage',
'by',
[]
],
[
'jvm.memory.heap.max',
'current heap usage',
'by',
[]
],
[
'jvm.memory.heap.used',
'current heap usage',
'by',
[]
],
[
'jvm.memory.nonheap.committed',
'current non-heap usage',
'by',
[]
],
[
'jvm.memory.nonheap.init',
'current non-heap usage',
'by',
[]
],
[
'jvm.memory.nonheap.max',
'current non-heap usage',
'by',
[]
],
[
'jvm.memory.nonheap.used',
'current non-heap usage',
'by',
[]
],
[
'jvm.memory.pool.committed',
'current memory pool usage',
'by',
[
"Code Cache",
"Par Eden Space",
"CMS Old Gen",
"Compressed Class Space",
"Metaspace",
"Par Survivor Space"]
],
[
'jvm.memory.pool.init',
'current memory pool usage',
'by',
[
"Code Cache",
"Par Eden Space",
"CMS Old Gen",
"Compressed Class Space",
"Metaspace",
"Par Survivor Space"]
],
[
'jvm.memory.pool.max',
'current memory pool usage',
'by',
[
"Code Cache",
"Par Eden Space",
"CMS Old Gen",
"Compressed Class Space",
"Metaspace",
"Par Survivor Space"]
],
[
'jvm.memory.pool.used',
'current memory pool usage',
'by',
[
"Code Cache",
"Par Eden Space",
"CMS Old Gen",
"Compressed Class Space",
"Metaspace",
"Par Survivor Space"]
],
[
'jvm.threads.count',
'number of threads',
'1',
[]
],
].eachWithIndex{ item, index ->
Metric metric = metrics.get(index)
assert metric.name == item[0]
assert metric.description == item[1]
assert metric.unit == item[2]
assert metric.hasIntSum()
IntSum datapoints = metric.intSum
def expectedLabelCount = item[3].size()
def expectedLabels = item[3] as Set

def expectedDatapointCount = expectedLabelCount == 0 ? 1 : expectedLabelCount
assert datapoints.dataPointsCount == expectedDatapointCount

(0..<expectedDatapointCount).each { i ->
def datapoint = datapoints.getDataPoints(i)
List<StringKeyValue> labels = datapoint.labelsList
if (expectedLabelCount != 0) {
assert labels.size() == 1
assert labels[0].key == 'name'
def value = labels[0].value
assert expectedLabels.remove(value)
} else {
assert labels.size() == 0
}
}

assert expectedLabels.size() == 0
}

cleanup:
cassandraContainer.stop()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
otel.jmx.interval.milliseconds = 3000
otel.exporter = prometheus
otel.prometheus.host = 0.0.0.0
otel.prometheus.port = 9123
otel.exporter = otlp
otel.jmx.service.url = service:jmx:rmi:///jndi/rmi://cassandra:7199/jmxrmi
otel.jmx.target.system = jvm

# these will be overridden by cmd line
otel.jmx.username = wrong_username
otel.jmx.password = wrong_password
otel.otlp.endpoint = host.testcontainers.internal:80

0 comments on commit 2ef6504

Please sign in to comment.