27
27
import com .splunk .opentelemetry .profiler .allocation .exporter .AllocationEventExporter ;
28
28
import com .splunk .opentelemetry .profiler .allocation .exporter .PprofAllocationEventExporter ;
29
29
import com .splunk .opentelemetry .profiler .context .SpanContextualizer ;
30
- import com .splunk .opentelemetry .profiler .events . EventPeriods ;
30
+ import com .splunk .opentelemetry .profiler .contextstorage . JavaContextStorage ;
31
31
import com .splunk .opentelemetry .profiler .exporter .CpuEventExporter ;
32
32
import com .splunk .opentelemetry .profiler .exporter .PprofCpuEventExporter ;
33
33
import com .splunk .opentelemetry .profiler .util .HelpfulExecutors ;
34
34
import io .opentelemetry .api .logs .Logger ;
35
+ import io .opentelemetry .api .trace .SpanContext ;
35
36
import io .opentelemetry .javaagent .extension .AgentListener ;
36
37
import io .opentelemetry .sdk .autoconfigure .AutoConfiguredOpenTelemetrySdk ;
37
38
import io .opentelemetry .sdk .autoconfigure .spi .ConfigProperties ;
45
46
import java .nio .file .Path ;
46
47
import java .nio .file .Paths ;
47
48
import java .time .Duration ;
49
+ import java .time .Instant ;
50
+ import java .util .HashMap ;
48
51
import java .util .Map ;
49
52
import java .util .concurrent .ExecutorService ;
53
+ import java .util .concurrent .ScheduledExecutorService ;
54
+ import java .util .concurrent .TimeUnit ;
50
55
51
56
@ AutoService (AgentListener .class )
52
57
public class JfrActivator implements AgentListener {
53
58
54
59
private static final java .util .logging .Logger logger =
55
60
java .util .logging .Logger .getLogger (JfrActivator .class .getName ());
56
- private final ExecutorService executor = HelpfulExecutors .newSingleThreadExecutor ("JFR Profiler" );
57
61
private final ConfigurationLogger configurationLogger = new ConfigurationLogger ();
58
62
59
63
@ Override
60
64
public void afterAgent (AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk ) {
61
65
ConfigProperties config = autoConfiguredOpenTelemetrySdk .getConfig ();
62
- if (notClearForTakeoff (config )) {
66
+ if (!config .getBoolean (CONFIG_KEY_ENABLE_PROFILER , false )) {
67
+ logger .fine ("Profiler is not enabled." );
63
68
return ;
64
69
}
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
+ }
65
80
66
81
configurationLogger .log (config );
67
82
logger .info ("Profiler is active." );
68
- executor .submit (
69
- logUncaught (
70
- () -> activateJfrAndRunForever (config , autoConfiguredOpenTelemetrySdk .getResource ())));
71
- }
72
83
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 ()) ;
77
88
}
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 )));
82
97
}
98
+ }
83
99
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
+ }
85
146
}
86
147
87
148
private boolean checkOutputDir (Path outputDir ) {
@@ -107,7 +168,7 @@ private boolean checkOutputDir(Path outputDir) {
107
168
return true ;
108
169
}
109
170
110
- private void outdirWarn (Path dir , String suffix ) {
171
+ private static void outdirWarn (Path dir , String suffix ) {
111
172
logger .log (WARNING , "The configured output directory {0} {1}." , new Object [] {dir , suffix });
112
173
}
113
174
@@ -128,13 +189,12 @@ private void activateJfrAndRunForever(ConfigProperties config, Resource resource
128
189
129
190
EventReader eventReader = new EventReader ();
130
191
SpanContextualizer spanContextualizer = new SpanContextualizer (eventReader );
131
- EventPeriods periods = new EventPeriods (jfrSettings ::get );
132
192
LogRecordExporter logsExporter = LogExporterBuilder .fromConfig (config );
133
193
134
194
CpuEventExporter cpuEventExporter =
135
195
PprofCpuEventExporter .builder ()
136
196
.otelLogger (buildOtelLogger (SimpleLogRecordProcessor .create (logsExporter ), resource ))
137
- .eventPeriods ( periods )
197
+ .period ( Configuration . getCallStackInterval ( config ) )
138
198
.stackDepth (stackDepth )
139
199
.build ();
140
200
0 commit comments