-
-
Notifications
You must be signed in to change notification settings - Fork 454
Feat/poc continuous profiling #4556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…sed on jfr converter bundled with asyncprofiler
… use existing SentryStackFrame instead of JfrFrame,
…inuous profiler in sentry init
…t in SentrySpan to work around scientific notation of double, use wall clock profiling
# Conflicts: # sentry/build.gradle.kts # sentry/src/test/java/io/sentry/ExternalOptionsTest.kt # sentry/src/test/java/io/sentry/JsonSerializerTest.kt # sentry/src/test/java/io/sentry/SentryClientTest.kt # sentry/src/test/java/io/sentry/SentryOptionsTest.kt
…using external options
…d of file extension
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
- Feat/poc continuous profiling ([#4556](https://github.com/getsentry/sentry-java/pull/4556)) If none of the above apply, you can opt out of this check by adding |
Performance metrics 🚀
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be deleted in follow-up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be renamed to SentryProfileSample or just SentrySample
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used anymore, will be deleted in follow-up PR. Instead using SentryStackFrame
now
return new JavaContinuousProfiler( | ||
logger, | ||
profilingTracesDirPath, | ||
10, // default profilingTracesHz |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in follow-up PR to use profilingTracesHz
instead of hardcoded 10
long divisor = jfr.ticksPerSec / 1000_000_000L; | ||
long myTimeStamp = | ||
jfr.chunkStartNanos + ((event.time - jfr.chunkStartTicks) / divisor); | ||
|
||
JfrSample sample = new JfrSample(); | ||
Instant instant = Instant.ofEpochSecond(0, myTimeStamp); | ||
double timestampDouble = | ||
instant.getEpochSecond() + instant.getNano() / 1_000_000_000.0; | ||
|
||
sample.timestamp = timestampDouble; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revisiting this in follow-up PR, I think the way the timestamp is calculated is not correct
@sentry review |
events.add(event); | ||
System.out.println(event); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The System.out.println(event)
statement should be removed or replaced with proper logging. Debug output should use the injected logger with appropriate log levels.
Did we get this right? 👍 / 👎 to inform future reviews.
import io.sentry.util.SentryRandom; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.ArrayList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded profiling frequency of 10 in the constructor call should use the provided profilingTracesHz
parameter instead of ignoring it.
Did we get this right? 👍 / 👎 to inform future reviews.
} else { | ||
frame.setInApp( | ||
new SentryStackTraceFactory(Sentry.getGlobalScope().getOptions()) | ||
.isInApp(sanitizedClassName)); | ||
} | ||
|
||
frame.setLineno((element.getLineNumber() != 0) ? element.getLineNumber() : null); | ||
frame.setFilename(classNameWithLambdas); | ||
|
||
if (sentryProfile.frames != null) { | ||
sentryProfile.frames.add(frame); | ||
} | ||
stack.add(currentFrame); | ||
currentFrame++; | ||
} | ||
|
||
long divisor = jfr.ticksPerSec / 1000_000_000L; | ||
long myTimeStamp = | ||
jfr.chunkStartNanos + ((event.time - jfr.chunkStartTicks) / divisor); | ||
|
||
JfrSample sample = new JfrSample(); | ||
Instant instant = Instant.ofEpochSecond(0, myTimeStamp); | ||
double timestampDouble = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conversion loop has performance concerns. Creating new SentryStackTraceFactory
and Instant
objects for each frame/sample is inefficient. Consider caching the SentryStackTraceFactory
and using more efficient timestamp conversion.
Did we get this right? 👍 / 👎 to inform future reviews.
|
||
if (profileChunk.getPlatform().equals("java")) { | ||
final IProfileConverter profileConverter = | ||
ProfilingServiceLoader.loadProfileConverter(); | ||
if (profileConverter != null) { | ||
try { | ||
final SentryProfile profile = | ||
profileConverter.convertFromFile(traceFile.toPath()); | ||
profileChunk.setSentryProfile(profile); | ||
} catch (IOException e) { | ||
throw new SentryEnvelopeException("Profile conversion failed"); | ||
} | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The profile conversion logic should have proper error handling and fallback. If conversion fails, the current implementation throws an exception, but it might be better to log the error and continue with a degraded experience.
Did we get this right? 👍 / 👎 to inform future reviews.
return NoOpContinuousProfiler.getInstance(); | ||
} catch (Throwable t) { | ||
logger.log( | ||
SentryLevel.ERROR, | ||
"Failed to load continuous profiler provider, using NoOpContinuousProfiler", | ||
t); | ||
return NoOpContinuousProfiler.getInstance(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service loader methods silently catch all Throwable
and either return null or a no-op implementation. This could hide important configuration or setup errors. Consider logging the errors at appropriate levels and being more specific about which exceptions to catch.
Did we get this right? 👍 / 👎 to inform future reviews.
startProfileChunkTimestamp = new SentryNanotimeDate(); | ||
} | ||
filename = profilingTracesDirPath + File.separator + SentryUUID.generateSentryId() + ".jfr"; | ||
String startData = null; | ||
try { | ||
final String profilingIntervalMicros = | ||
String.format("%dus", (int) SECONDS.toMicros(1) / profilingTracesHz); | ||
final String command = | ||
String.format( | ||
"start,jfr,event=wall,interval=%s,file=%s", profilingIntervalMicros, filename); | ||
System.out.println(command); | ||
startData = profiler.execute(command); | ||
} catch (Exception e) { | ||
logger.log(SentryLevel.ERROR, "Failed to start profiling: ", e); | ||
} | ||
// check if profiling started | ||
if (startData == null) { | ||
return; | ||
} | ||
|
||
isRunning = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The profiler initialization and AsyncProfiler command construction should have more robust error handling. The current implementation may not handle all edge cases properly.
Did we get this right? 👍 / 👎 to inform future reviews.
📜 Description
💡 Motivation and Context
Initial implementation of #2635
💚 How did you test it?
📝 Checklist
sendDefaultPII
is enabled.🔮 Next steps