22
33import static io .sentry .DataCategory .All ;
44import static io .sentry .IConnectionStatusProvider .ConnectionStatus .DISCONNECTED ;
5+ import static java .util .concurrent .TimeUnit .SECONDS ;
56
67import io .sentry .DataCategory ;
78import io .sentry .IContinuousProfiler ;
@@ -55,6 +56,7 @@ public final class JavaContinuousProfiler
5556 private @ NotNull SentryId chunkId = SentryId .EMPTY_ID ;
5657 private final @ NotNull AtomicBoolean isClosed = new AtomicBoolean (false );
5758 private @ NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate ();
59+ private final @ NotNull String profilingIntervalMicros ;
5860
5961 private @ NotNull String filename = "" ;
6062
@@ -77,6 +79,8 @@ public JavaContinuousProfiler(
7779 this .profilingTracesHz = profilingTracesHz ;
7880 this .executorService = executorService ;
7981 this .profiler = AsyncProfiler .getInstance ();
82+ this .profilingIntervalMicros =
83+ String .format ("%dus" , (int ) SECONDS .toMicros (1 ) / profilingTracesHz );
8084 }
8185
8286 private void init () {
@@ -108,23 +112,42 @@ public void startProfiler(
108112 try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
109113 if (shouldSample ) {
110114 isSampled = tracesSampler .sampleSessionProfile (SentryRandom .current ().nextDouble ());
111- // Kepp TRUE for now
112- // shouldSample = false;
115+ shouldSample = false ;
113116 }
114117 if (!isSampled ) {
115118 logger .log (SentryLevel .DEBUG , "Profiler was not started due to sampling decision." );
116119 return ;
117120 }
118121
122+ switch (profileLifecycle ) {
123+ case TRACE :
124+ // rootSpanCounter should never be negative, unless the user changed profile lifecycle
125+ // while
126+ // the profiler is running or close() is called. This is just a safety check.
127+ if (rootSpanCounter < 0 ) {
128+ rootSpanCounter = 0 ;
129+ }
130+ rootSpanCounter ++;
131+ break ;
132+ case MANUAL :
133+ // We check if the profiler is already running and log a message only in manual mode,
134+ // since
135+ // in trace mode we can have multiple concurrent traces
136+ if (isRunning ()) {
137+ logger .log (SentryLevel .DEBUG , "Profiler is already running." );
138+ return ;
139+ }
140+ break ;
141+ }
142+
119143 if (!isRunning ()) {
120144 logger .log (SentryLevel .DEBUG , "Started Profiler." );
121145 start ();
122146 }
123147 }
124148 }
125149
126- @ SuppressWarnings ("ReferenceEquality" )
127- private void start () {
150+ private void initScopes () {
128151 if ((scopes == null || scopes == NoOpScopes .getInstance ())
129152 && Sentry .getCurrentScopes () != NoOpScopes .getInstance ()) {
130153 this .scopes = Sentry .forkedRootScopes ("profiler" );
@@ -133,13 +156,14 @@ private void start() {
133156 rateLimiter .addRateLimitObserver (this );
134157 }
135158 }
159+ }
160+
161+ @ SuppressWarnings ("ReferenceEquality" )
162+ private void start () {
163+ initScopes ();
136164
137165 // Let's initialize trace folder and profiling interval
138166 init ();
139- // init() didn't create profiler, should never happen
140- if (profiler == null ) {
141- return ;
142- }
143167
144168 if (scopes != null ) {
145169 final @ Nullable RateLimiter rateLimiter = scopes .getRateLimiter ();
@@ -152,6 +176,7 @@ private void start() {
152176 return ;
153177 }
154178
179+ // TODO: Taken from the android profiler, do we need this on the JVM as well?
155180 // If device is offline, we don't start the profiler, to avoid flooding the cache
156181 if (scopes .getOptions ().getConnectionStatusProvider ().getConnectionStatus () == DISCONNECTED ) {
157182 logger .log (SentryLevel .WARNING , "Device is offline. Stopping profiler." );
@@ -166,7 +191,13 @@ private void start() {
166191 filename = SentryUUID .generateSentryId () + ".jfr" ;
167192 final String startData ;
168193 try {
169- startData = profiler .execute ("start,jfr,event=wall,file=" + filename );
194+ // final String command =
195+ // String.format("start,jfr,event=cpu,wall=%s,file=%s",profilingIntervalMicros, filename);
196+ final String command =
197+ String .format (
198+ "start,jfr,event=wall,interval=%s,file=%s" , profilingIntervalMicros , filename );
199+ System .out .println (command );
200+ startData = profiler .execute (command );
170201 } catch (IOException e ) {
171202 throw new RuntimeException (e );
172203 }
@@ -177,7 +208,7 @@ private void start() {
177208
178209 isRunning = true ;
179210
180- if (SentryId .EMPTY_ID . equals ( profilerId ) ) {
211+ if (profilerId == SentryId .EMPTY_ID ) {
181212 profilerId = new SentryId ();
182213 }
183214
@@ -199,7 +230,24 @@ private void start() {
199230 @ Override
200231 public void stopProfiler (final @ NotNull ProfileLifecycle profileLifecycle ) {
201232 try (final @ NotNull ISentryLifecycleToken ignored = lock .acquire ()) {
202- shouldStop = true ;
233+ switch (profileLifecycle ) {
234+ case TRACE :
235+ rootSpanCounter --;
236+ // If there are active spans, and profile lifecycle is trace, we don't stop the profiler
237+ if (rootSpanCounter > 0 ) {
238+ return ;
239+ }
240+ // rootSpanCounter should never be negative, unless the user changed profile lifecycle
241+ // while the profiler is running or close() is called. This is just a safety check.
242+ if (rootSpanCounter < 0 ) {
243+ rootSpanCounter = 0 ;
244+ }
245+ shouldStop = true ;
246+ break ;
247+ case MANUAL :
248+ shouldStop = true ;
249+ break ;
250+ }
203251 }
204252 }
205253
@@ -209,7 +257,7 @@ private void stop(final boolean restartProfiler) {
209257 stopFuture .cancel (true );
210258 }
211259 // check if profiler was created and it's running
212- if (profiler == null || !isRunning ) {
260+ if (!isRunning ) {
213261 // When the profiler is stopped due to an error (e.g. offline or rate limited), reset the
214262 // ids
215263 profilerId = SentryId .EMPTY_ID ;
@@ -333,11 +381,11 @@ public int getRootSpanCounter() {
333381 @ Override
334382 public void onRateLimitChanged (@ NotNull RateLimiter rateLimiter ) {
335383 // We stop the profiler as soon as we are rate limited, to avoid the performance overhead
336- // if (rateLimiter.isActiveForCategory(All)
337- // || rateLimiter.isActiveForCategory(DataCategory.ProfileChunk)) {
338- // logger.log(SentryLevel.WARNING, "SDK is rate limited. Stopping profiler.");
339- // stop(false);
340- // }
384+ if (rateLimiter .isActiveForCategory (All )
385+ || rateLimiter .isActiveForCategory (DataCategory .ProfileChunk )) {
386+ logger .log (SentryLevel .WARNING , "SDK is rate limited. Stopping profiler." );
387+ stop (false );
388+ }
341389 // If we are not rate limited anymore, we don't do anything: the profile is broken, so it's
342390 // useless to restart it automatically
343391 }
0 commit comments