2727import com .splunk .opentelemetry .profiler .allocation .exporter .AllocationEventExporter ;
2828import com .splunk .opentelemetry .profiler .allocation .exporter .PprofAllocationEventExporter ;
2929import com .splunk .opentelemetry .profiler .context .SpanContextualizer ;
30- import com .splunk .opentelemetry .profiler .events . EventPeriods ;
30+ import com .splunk .opentelemetry .profiler .contextstorage . JavaContextStorage ;
3131import com .splunk .opentelemetry .profiler .exporter .CpuEventExporter ;
3232import com .splunk .opentelemetry .profiler .exporter .PprofCpuEventExporter ;
3333import com .splunk .opentelemetry .profiler .util .HelpfulExecutors ;
3434import io .opentelemetry .api .logs .Logger ;
35+ import io .opentelemetry .api .trace .SpanContext ;
3536import io .opentelemetry .javaagent .extension .AgentListener ;
3637import io .opentelemetry .sdk .autoconfigure .AutoConfiguredOpenTelemetrySdk ;
3738import io .opentelemetry .sdk .autoconfigure .spi .ConfigProperties ;
4546import java .nio .file .Path ;
4647import java .nio .file .Paths ;
4748import java .time .Duration ;
49+ import java .time .Instant ;
50+ import java .util .HashMap ;
4851import java .util .Map ;
4952import java .util .concurrent .ExecutorService ;
53+ import java .util .concurrent .ScheduledExecutorService ;
54+ import java .util .concurrent .TimeUnit ;
5055
5156@ AutoService (AgentListener .class )
5257public class JfrActivator implements AgentListener {
5358
5459 private static final java .util .logging .Logger logger =
5560 java .util .logging .Logger .getLogger (JfrActivator .class .getName ());
56- private final ExecutorService executor = HelpfulExecutors .newSingleThreadExecutor ("JFR Profiler" );
5761 private final ConfigurationLogger configurationLogger = new ConfigurationLogger ();
5862
5963 @ Override
6064 public void afterAgent (AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk ) {
6165 ConfigProperties config = autoConfiguredOpenTelemetrySdk .getConfig ();
62- if (notClearForTakeoff (config )) {
66+ if (!config .getBoolean (CONFIG_KEY_ENABLE_PROFILER , false )) {
67+ logger .fine ("Profiler is not enabled." );
6368 return ;
6469 }
70+ boolean useJfr = Configuration .getProfilerJfrEnabled (config );
71+ if (useJfr && !JFR .instance .isAvailable ()) {
72+ logger .fine (
73+ "JDK Flight Recorder (JFR) is not available in this JVM, switching to java profiler." );
74+ if (Configuration .getTLABEnabled (config )) {
75+ logger .warning (
76+ "JDK Flight Recorder (JFR) is not available in this JVM. Memory profiling is disabled." );
77+ }
78+ useJfr = false ;
79+ }
6580
6681 configurationLogger .log (config );
6782 logger .info ("Profiler is active." );
68- executor .submit (
69- logUncaught (
70- () -> activateJfrAndRunForever (config , autoConfiguredOpenTelemetrySdk .getResource ())));
71- }
7283
73- private boolean notClearForTakeoff ( ConfigProperties config ) {
74- if (! config . getBoolean ( CONFIG_KEY_ENABLE_PROFILER , false )) {
75- logger . fine ( "Profiler is not enabled." );
76- return true ;
84+ if ( useJfr ) {
85+ JfrProfiler . run ( this , config , autoConfiguredOpenTelemetrySdk . getResource ());
86+ } else {
87+ JavaProfiler . run ( this , config , autoConfiguredOpenTelemetrySdk . getResource ()) ;
7788 }
78- if (!JFR .instance .isAvailable ()) {
79- logger .warning (
80- "JDK Flight Recorder (JFR) is not available in this JVM. Profiling is disabled." );
81- return true ;
89+ }
90+
91+ private static class JfrProfiler {
92+ private static final ExecutorService executor =
93+ HelpfulExecutors .newSingleThreadExecutor ("JFR Profiler" );
94+
95+ static void run (JfrActivator activator , ConfigProperties config , Resource resource ) {
96+ executor .submit (logUncaught (() -> activator .activateJfrAndRunForever (config , resource )));
8297 }
98+ }
8399
84- return false ;
100+ private static class JavaProfiler {
101+ private static final ScheduledExecutorService scheduler =
102+ HelpfulExecutors .newSingleThreadedScheduledExecutor ("Profiler scheduler" );
103+
104+ static void run (JfrActivator activator , ConfigProperties config , Resource resource ) {
105+ int stackDepth = Configuration .getStackDepth (config );
106+ LogRecordExporter logsExporter = LogExporterBuilder .fromConfig (config );
107+ CpuEventExporter cpuEventExporter =
108+ PprofCpuEventExporter .builder ()
109+ .otelLogger (
110+ activator .buildOtelLogger (
111+ SimpleLogRecordProcessor .create (logsExporter ), resource ))
112+ .period (Configuration .getCallStackInterval (config ))
113+ .stackDepth (stackDepth )
114+ .build ();
115+
116+ Runnable profiler =
117+ () -> {
118+ Instant now = Instant .now ();
119+ Map <Thread , StackTraceElement []> stackTracesMap ;
120+ Map <Thread , SpanContext > contextMap = new HashMap <>();
121+ // disallow context changes while we are taking the thread dump
122+ JavaContextStorage .block ();
123+ try {
124+ stackTracesMap = Thread .getAllStackTraces ();
125+ // copy active context for each thread
126+ for (Thread thread : stackTracesMap .keySet ()) {
127+ SpanContext spanContext = JavaContextStorage .activeContext .get (thread );
128+ if (spanContext != null ) {
129+ contextMap .put (thread , spanContext );
130+ }
131+ }
132+ } finally {
133+ JavaContextStorage .unblock ();
134+ }
135+ for (Map .Entry <Thread , StackTraceElement []> entry : stackTracesMap .entrySet ()) {
136+ Thread thread = entry .getKey ();
137+ SpanContext spanContext = contextMap .get (thread );
138+ cpuEventExporter .export (thread , entry .getValue (), now , spanContext );
139+ }
140+ cpuEventExporter .flush ();
141+ };
142+ long period = Configuration .getCallStackInterval (config ).toMillis ();
143+ scheduler .scheduleAtFixedRate (
144+ logUncaught (() -> profiler .run ()), period , period , TimeUnit .MILLISECONDS );
145+ }
85146 }
86147
87148 private boolean checkOutputDir (Path outputDir ) {
@@ -107,7 +168,7 @@ private boolean checkOutputDir(Path outputDir) {
107168 return true ;
108169 }
109170
110- private void outdirWarn (Path dir , String suffix ) {
171+ private static void outdirWarn (Path dir , String suffix ) {
111172 logger .log (WARNING , "The configured output directory {0} {1}." , new Object [] {dir , suffix });
112173 }
113174
@@ -128,13 +189,12 @@ private void activateJfrAndRunForever(ConfigProperties config, Resource resource
128189
129190 EventReader eventReader = new EventReader ();
130191 SpanContextualizer spanContextualizer = new SpanContextualizer (eventReader );
131- EventPeriods periods = new EventPeriods (jfrSettings ::get );
132192 LogRecordExporter logsExporter = LogExporterBuilder .fromConfig (config );
133193
134194 CpuEventExporter cpuEventExporter =
135195 PprofCpuEventExporter .builder ()
136196 .otelLogger (buildOtelLogger (SimpleLogRecordProcessor .create (logsExporter ), resource ))
137- .eventPeriods ( periods )
197+ .period ( Configuration . getCallStackInterval ( config ) )
138198 .stackDepth (stackDepth )
139199 .build ();
140200
0 commit comments