-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c0f3e5f
commit f060c09
Showing
3 changed files
with
190 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,58 @@ | ||
sourceSets { | ||
java17 { | ||
java { | ||
srcDirs = ['src/main/java17'] | ||
compileClasspath = configurations.compileClasspath | ||
runtimeClasspath = configurations.runtimeClasspath | ||
} | ||
} | ||
java17Test { | ||
java { | ||
srcDirs = ['src/test/java17'] | ||
compileClasspath = jar.outputs.files + configurations.testCompileClasspath | ||
runtimeClasspath = jar.outputs.files + runtimeClasspath + configurations.testRuntimeClasspath | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
api project(':spectator-api') | ||
implementation 'com.typesafe:config' | ||
} | ||
|
||
jar { | ||
def java17Compiler = javaToolchains.compilerFor { | ||
languageVersion = JavaLanguageVersion.of(17) | ||
} | ||
|
||
tasks.named('compileJava17Java', JavaCompile).configure { | ||
javaCompiler = java17Compiler | ||
} | ||
|
||
tasks.named('compileJava17TestJava', JavaCompile).configure { | ||
javaCompiler = java17Compiler | ||
} | ||
|
||
tasks.named('jar').configure { | ||
into('META-INF/versions/17') { | ||
from sourceSets.java17.output | ||
} | ||
manifest { | ||
attributes( | ||
"Automatic-Module-Name": "com.netflix.spectator.jvm" | ||
'Automatic-Module-Name': 'com.netflix.spectator.jvm', | ||
'Multi-Release': 'true' | ||
) | ||
} | ||
} | ||
|
||
def testJava17 = tasks.register('testJava17', Test) { | ||
description = "Runs tests for java17Test sourceset." | ||
group = 'verification' | ||
|
||
testClassesDirs = sourceSets.java17Test.output.classesDirs | ||
classpath = sourceSets.java17Test.runtimeClasspath | ||
|
||
javaLauncher = javaToolchains.launcherFor { | ||
languageVersion = JavaLanguageVersion.of(17) | ||
} | ||
} | ||
check.dependsOn testJava17 |
107 changes: 107 additions & 0 deletions
107
spectator-ext-jvm/src/main/java17/com/netflix/spectator/jvm/JavaFlightRecorder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package com.netflix.spectator.jvm; | ||
|
||
import com.netflix.spectator.api.Registry; | ||
import jdk.jfr.EventSettings; | ||
import jdk.jfr.consumer.RecordedEvent; | ||
import jdk.jfr.consumer.RecordingStream; | ||
|
||
import java.time.Duration; | ||
import java.util.Objects; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.function.Consumer; | ||
|
||
/** | ||
* Helpers supporting continuous monitoring with Java Flight Recorder. | ||
*/ | ||
public class JavaFlightRecorder { | ||
|
||
private static final String PREFIX = "jdk."; | ||
private static final String CPULoad = PREFIX + "CPULoad"; | ||
private static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned"; | ||
private static final String VirtualThreadSubmitFailed = PREFIX + "VirtualThreadSubmitFailed"; | ||
private static final String ZAllocationStall = PREFIX + "ZAllocationStall"; | ||
private static final String ZYoungGarbageCollection = PREFIX + "ZYoungGarbageCollection"; | ||
private static final String ZOldGarbageCollection = PREFIX + "ZOldGarbageCollection"; | ||
|
||
private JavaFlightRecorder() { | ||
} | ||
|
||
/** | ||
* Collect low-overhead Java Flight Recorder events, using the provided | ||
* {@link Executor} to execute a single task to collect events. | ||
* | ||
* @param registry the registry | ||
* @param executor the executor to execute the task for streaming events | ||
* @return an {@link AutoCloseable} allowing the underlying | ||
* {@link jdk.jfr.consumer.EventStream} to be closed | ||
*/ | ||
public static AutoCloseable monitorDefaultEvents(Registry registry, Executor executor) { | ||
Objects.requireNonNull(registry); | ||
Objects.requireNonNull(executor); | ||
RecordingStream rs = new RecordingStream(); | ||
collectCpuEvents(registry, rs); | ||
collectVirtualThreadEvents(registry, rs); | ||
collectZgcEvents(registry, rs); | ||
executor.execute(rs::start); | ||
return rs::close; | ||
} | ||
|
||
private static void collectCpuEvents(Registry registry, RecordingStream rs) { | ||
consume(CPULoad, rs, event -> { | ||
String pid = "unknown"; | ||
try { | ||
pid = String.valueOf(ProcessHandle.current().pid()); | ||
} catch (UnsupportedOperationException ignore) { | ||
} | ||
registry.gauge("jvm.cpuLoad", "type", "jvmUser", "pid", pid) | ||
.set(event.getFloat("jvmUser")); | ||
registry.gauge("jvm.cpuLoad", "type", "jvmSystem", "pid", pid) | ||
.set(event.getFloat("jvmSystem")); | ||
registry.gauge("jvm.cpuLoad", "type", "machineTotal", "pid", pid) | ||
.set(event.getFloat("machineTotal")); | ||
}).withPeriod(Duration.ofSeconds(1)); | ||
} | ||
|
||
private static void collectVirtualThreadEvents(Registry registry, RecordingStream rs) { | ||
consume(VirtualThreadPinned, rs, event -> | ||
registry.timer("jvm.vt.pinned").record(event.getDuration()) | ||
).withThreshold(Duration.ofMillis(20)); | ||
consume(VirtualThreadSubmitFailed, rs, event -> | ||
registry.counter("jvm.vt.submitFailed").increment() | ||
); | ||
} | ||
|
||
private static void collectZgcEvents(Registry registry, RecordingStream rs) { | ||
consume(ZYoungGarbageCollection, rs, event -> | ||
registry.timer("jvm.zgc.youngCollection", "tenuringThreshold", event.getString("tenuringThreshold")) | ||
.record(event.getDuration())); | ||
|
||
consume(ZOldGarbageCollection, rs, event -> | ||
registry.timer("jvm.zgc.oldCollection") | ||
.record(event.getDuration())); | ||
|
||
consume(ZAllocationStall, rs, event -> | ||
registry.timer("jvm.zgc.allocationStall", "type", event.getString("type")) | ||
.record(event.getDuration())); | ||
} | ||
|
||
/** | ||
* Consume a given JFR event. For full event details see the event definitions and default/profiling configuration: | ||
* <p> | ||
* - <a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/jfr/metadata/metadata.xml">metadata.xml</a> | ||
* - <a href="https://github.com/openjdk/jdk/blob/master/src/jdk.jfr/share/conf/jfr/default.jfc">default.jfc</a> | ||
* - <a href="https://github.com/openjdk/jdk/blob/master/src/jdk.jfr/share/conf/jfr/profile.jfc">profile.jfc</a> | ||
* <p> | ||
* We avoid the default event configurations because despite their claims of "low-overhead" there are | ||
* situtations where they can impose significant overhead to the application. | ||
*/ | ||
private static EventSettings consume(String name, RecordingStream rs, Consumer<RecordedEvent> consumer) { | ||
EventSettings settings = rs.enable(name) | ||
.withoutStackTrace() // ensure no events have stacktraces accidentally enabled | ||
.withThreshold(Duration.ofMillis(0)); | ||
rs.onEvent(name, consumer); | ||
return settings; | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
spectator-ext-jvm/src/test/java17/com/netflix/spectator/jvm/JavaFlightRecorderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.netflix.spectator.jvm; | ||
|
||
import com.netflix.spectator.api.DefaultRegistry; | ||
import com.netflix.spectator.api.Id; | ||
import com.netflix.spectator.api.Measurement; | ||
import com.netflix.spectator.api.Registry; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.List; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
public class JavaFlightRecorderTest { | ||
|
||
@Test | ||
public void test() throws Exception { | ||
Registry registry = new DefaultRegistry(); | ||
ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
try (var closable = JavaFlightRecorder.monitorDefaultEvents(registry, executor)) { | ||
Thread.sleep(2000); | ||
} | ||
executor.shutdownNow(); | ||
|
||
Assertions.assertEquals(3, registry.measurements().count()); | ||
List<String> distinctMeasures = registry.measurements() | ||
.map(Measurement::id) | ||
.map(Id::name) | ||
.distinct() | ||
.toList(); | ||
Assertions.assertEquals(1, distinctMeasures.size()); | ||
Assertions.assertEquals("jvm.cpuLoad", distinctMeasures.get(0)); | ||
} | ||
|
||
} |