2424import io .sentry .util .AutoClosableReentrantLock ;
2525import io .sentry .util .SentryRandom ;
2626import java .io .File ;
27- import java .io .IOException ;
2827import java .util .ArrayList ;
2928import java .util .HashMap ;
3029import java .util .List ;
@@ -52,13 +51,12 @@ public final class JavaContinuousProfiler
5251 private @ Nullable Future <?> stopFuture ;
5352 private final @ NotNull List <ProfileChunk .Builder > payloadBuilders = new ArrayList <>();
5453 private @ NotNull SentryId profilerId = SentryId .EMPTY_ID ;
55- private @ NotNull SentryId chunkId = SentryId .EMPTY_ID ;
5654 private final @ NotNull AtomicBoolean isClosed = new AtomicBoolean (false );
5755 private @ NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate ();
5856
5957 private @ NotNull String filename = "" ;
6058
61- private final @ NotNull AsyncProfiler profiler ;
59+ private @ Nullable AsyncProfiler profiler ;
6260 private volatile boolean shouldSample = true ;
6361 private boolean shouldStop = false ;
6462 private boolean isSampled = false ;
@@ -76,21 +74,52 @@ public JavaContinuousProfiler(
7674 this .profilingTracesDirPath = profilingTracesDirPath ;
7775 this .profilingTracesHz = profilingTracesHz ;
7876 this .executorService = executorService ;
79- this .profiler = AsyncProfiler .getInstance ();
77+ initializeProfiler ();
78+ }
79+
80+ private void initializeProfiler () {
81+ try {
82+ this .profiler = AsyncProfiler .getInstance ();
83+ // Check version to verify profiler is working
84+ String version = profiler .execute ("version" );
85+ logger .log (SentryLevel .DEBUG , "AsyncProfiler initialized successfully. Version: " + version );
86+ } catch (Exception e ) {
87+ logger .log (
88+ SentryLevel .WARNING ,
89+ "Failed to initialize AsyncProfiler. Profiling will be disabled." ,
90+ e );
91+ this .profiler = null ;
92+ }
8093 }
8194
8295 private boolean init () {
83- // We initialize it only once
8496 if (isInitialized ) {
8597 return true ;
8698 }
8799 isInitialized = true ;
100+
101+ if (profiler == null ) {
102+ logger .log (SentryLevel .ERROR , "Disabling profiling because AsyncProfiler is not available." );
103+ return false ;
104+ }
105+
88106 if (profilingTracesDirPath == null ) {
89107 logger .log (
90108 SentryLevel .WARNING ,
91109 "Disabling profiling because no profiling traces dir path is defined in options." );
92110 return false ;
93111 }
112+
113+ File profileDir = new File (profilingTracesDirPath );
114+
115+ if (!profileDir .canWrite () || !profileDir .exists ()) {
116+ logger .log (
117+ SentryLevel .WARNING ,
118+ "Disabling profiling because traces directory is not writable or does not exist: %s" ,
119+ profilingTracesDirPath );
120+ return false ;
121+ }
122+
94123 if (profilingTracesHz <= 0 ) {
95124 logger .log (
96125 SentryLevel .WARNING ,
@@ -101,7 +130,6 @@ private boolean init() {
101130 return true ;
102131 }
103132
104- @ SuppressWarnings ("ReferenceEquality" )
105133 @ Override
106134 public void startProfiler (
107135 final @ NotNull ProfileLifecycle profileLifecycle ,
@@ -138,6 +166,7 @@ public void startProfiler(
138166 }
139167
140168 if (!isRunning ()) {
169+ shouldStop = false ;
141170 logger .log (SentryLevel .DEBUG , "Started Profiler." );
142171 start ();
143172 }
@@ -159,7 +188,6 @@ private void initScopes() {
159188 private void start () {
160189 initScopes ();
161190
162- // Let's initialize trace folder and profiling interval
163191 if (!init ()) {
164192 return ;
165193 }
@@ -179,20 +207,31 @@ private void start() {
179207 } else {
180208 startProfileChunkTimestamp = new SentryNanotimeDate ();
181209 }
210+
211+ if (profiler == null ) {
212+ logger .log (SentryLevel .ERROR , "Cannot start profiling: AsyncProfiler is not available" );
213+ return ;
214+ }
215+
182216 filename = profilingTracesDirPath + File .separator + SentryUUID .generateSentryId () + ".jfr" ;
183- String startData = null ;
217+
218+ File jfrFile = new File (filename );
219+
184220 try {
185221 final String profilingIntervalMicros =
186222 String .format ("%dus" , (int ) SECONDS .toMicros (1 ) / profilingTracesHz );
187223 final String command =
188224 String .format ("start,jfr,wall=%s,file=%s" , profilingIntervalMicros , filename );
189- System .out .println (command );
190- startData = profiler .execute (command );
225+
226+ profiler .execute (command );
227+
191228 } catch (Exception e ) {
192229 logger .log (SentryLevel .ERROR , "Failed to start profiling: " , e );
193- }
194- // check if profiling started
195- if (startData == null ) {
230+ filename = "" ;
231+ // Try to clean up the file if it was created
232+ if (jfrFile .exists ()) {
233+ jfrFile .delete ();
234+ }
196235 return ;
197236 }
198237
@@ -202,10 +241,6 @@ private void start() {
202241 profilerId = new SentryId ();
203242 }
204243
205- if (chunkId == SentryId .EMPTY_ID ) {
206- chunkId = new SentryId ();
207- }
208-
209244 try {
210245 stopFuture = executorService .schedule (() -> stop (true ), MAX_CHUNK_DURATION_MILLIS );
211246 } catch (RejectedExecutionException e ) {
@@ -249,45 +284,57 @@ private void stop(final boolean restartProfiler) {
249284 // check if profiler was created and it's running
250285 if (!isRunning ) {
251286 // When the profiler is stopped due to an error (e.g. offline or rate limited), reset the
252- // ids
287+ // id
253288 profilerId = SentryId .EMPTY_ID ;
254- chunkId = SentryId .EMPTY_ID ;
255289 return ;
256290 }
257291
258- String endData = null ;
292+ File jfrFile = new File (filename );
293+
294+ if (profiler == null ) {
295+ logger .log (SentryLevel .WARNING , "Profiler is null when trying to stop" );
296+ return ;
297+ }
298+
259299 try {
260- endData = profiler .execute ("stop,jfr" );
261- } catch (IOException e ) {
262- throw new RuntimeException (e );
300+ profiler .execute ("stop,jfr" );
301+ } catch (Exception e ) {
302+ logger .log (SentryLevel .ERROR , "Error stopping profiler, attempting cleanup: " , e );
303+ // Clean up file if it exists
304+ if (jfrFile .exists ()) {
305+ jfrFile .delete ();
306+ }
263307 }
264308
265- // check if profiler end successfully
266- if (endData == null ) {
267- logger .log (
268- SentryLevel .ERROR ,
269- "An error occurred while collecting a profile chunk, and it won't be sent." );
270- } else {
271- // The scopes can be null if the profiler is started before the SDK is initialized (app
272- // start profiling), meaning there's no scopes to send the chunks. In that case, we store
273- // the data in a list and send it when the next chunk is finished.
309+ // The scopes can be null if the profiler is started before the SDK is initialized (app
310+ // start profiling), meaning there's no scopes to send the chunks. In that case, we store
311+ // the data in a list and send it when the next chunk is finished.
312+ if (jfrFile .exists () && jfrFile .canRead () && jfrFile .length () > 0 ) {
274313 try (final @ NotNull ISentryLifecycleToken ignored2 = payloadLock .acquire ()) {
275- File jfrFile = new File (filename );
276314 jfrFile .deleteOnExit ();
277315 payloadBuilders .add (
278316 new ProfileChunk .Builder (
279317 profilerId ,
280- chunkId ,
318+ new SentryId () ,
281319 new HashMap <>(),
282320 jfrFile ,
283321 startProfileChunkTimestamp ,
284- ProfileChunk .PLATFORM_ANDROID ));
322+ ProfileChunk .PLATFORM_JAVA ));
323+ }
324+ } else {
325+ logger .log (
326+ SentryLevel .WARNING ,
327+ "JFR file is invalid or empty: exists=%b, readable=%b, size=%d" ,
328+ jfrFile .exists (),
329+ jfrFile .canRead (),
330+ jfrFile .length ());
331+ if (jfrFile .exists ()) {
332+ jfrFile .delete ();
285333 }
286334 }
287335
336+ // Always clean up state, even if stop failed
288337 isRunning = false ;
289- // A chunk is finished. Next chunk will have a different id.
290- chunkId = SentryId .EMPTY_ID ;
291338 filename = "" ;
292339
293340 if (scopes != null ) {
@@ -356,7 +403,9 @@ private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOpti
356403
357404 @ Override
358405 public boolean isRunning () {
359- return isRunning ;
406+ try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
407+ return isRunning && profiler != null && !filename .isEmpty ();
408+ }
360409 }
361410
362411 @ VisibleForTesting
0 commit comments