-
-
Notifications
You must be signed in to change notification settings - Fork 457
Profiling - Deduplication and cleanup #4681
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: feat/continuous-profiling-02
Are you sure you want to change the base?
Profiling - Deduplication and cleanup #4681
Conversation
…with cross platform frameworks
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
- Profiling - Deduplication and cleanup ([#4681](https://github.com/getsentry/sentry-java/pull/4681)) If none of the above apply, you can opt out of this check by adding |
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d6f8356 | 392.08 ms | 454.42 ms | 62.34 ms |
066d89d | 377.51 ms | 434.20 ms | 56.69 ms |
7dcbbbd | 459.22 ms | 509.85 ms | 50.62 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d6f8356 | 1.58 MiB | 2.09 MiB | 521.68 KiB |
066d89d | 1.58 MiB | 2.09 MiB | 521.85 KiB |
7dcbbbd | 1.58 MiB | 2.09 MiB | 521.68 KiB |
…deduplicate stacks
…entry-java into feat/continuous-profiling-03
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
@sentry review |
cursor review |
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.
✅ Bugbot reviewed your changes and found no bugs!
public static final String POST_CONTEXT = "post_context"; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (o == null || getClass() != o.getClass()) return false; | ||
SentryStackFrame that = (SentryStackFrame) o; | ||
return Objects.equals(preContext, that.preContext) | ||
&& Objects.equals(postContext, that.postContext) | ||
&& Objects.equals(vars, that.vars) | ||
&& Objects.equals(framesOmitted, that.framesOmitted) | ||
&& Objects.equals(filename, that.filename) | ||
&& Objects.equals(function, that.function) | ||
&& Objects.equals(module, that.module) | ||
&& Objects.equals(lineno, that.lineno) | ||
&& Objects.equals(colno, that.colno) | ||
&& Objects.equals(absPath, that.absPath) | ||
&& Objects.equals(contextLine, that.contextLine) | ||
&& Objects.equals(inApp, that.inApp) | ||
&& Objects.equals(_package, that._package) | ||
&& Objects.equals(_native, that._native) | ||
&& Objects.equals(platform, that.platform) | ||
&& Objects.equals(imageAddr, that.imageAddr) | ||
&& Objects.equals(symbolAddr, that.symbolAddr) | ||
&& Objects.equals(instructionAddr, that.instructionAddr) | ||
&& Objects.equals(addrMode, that.addrMode) | ||
&& Objects.equals(symbol, that.symbol) | ||
&& Objects.equals(unknown, that.unknown) | ||
&& Objects.equals(rawFunction, that.rawFunction) | ||
&& Objects.equals(lock, that.lock); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash( | ||
preContext, | ||
postContext, | ||
vars, | ||
framesOmitted, | ||
filename, | ||
function, | ||
module, | ||
lineno, | ||
colno, | ||
absPath, | ||
contextLine, | ||
inApp, | ||
_package, | ||
_native, | ||
platform, | ||
imageAddr, | ||
symbolAddr, | ||
instructionAddr, | ||
addrMode, | ||
symbol, | ||
unknown, | ||
rawFunction, | ||
lock); | ||
} |
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 vars
field is documented as Map<String, String>
in the class comment but the actual field type has changed to Map<String, Object>
according to the repository context. This mismatch could lead to confusion. The equals and hashCode methods should exclude mutable fields that can change during the object's lifetime, but here vars
is included which may cause issues if the map is modified after the object's hash is computed.
Did we get this right? 👍 / 👎 to inform future reviews.
import java.util.List; | ||
import java.util.Map; |
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 frame deduplication using HashMap
with SentryStackFrame
as key relies on the newly added equals
/hashCode
methods. However, if any of the frame fields are mutable and change after being used as a key, this could lead to inconsistent behavior. Consider making the frame objects immutable after creation or using a different deduplication strategy.
Did we get this right? 👍 / 👎 to inform future reviews.
🚨 Detected changes in high risk code 🚨High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:
|
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.
WIP
} | ||
} | ||
|
||
private long resolveThreadId(int eventThreadId) { | ||
return jfr.threads.get(eventThreadId) != null |
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.
l
could extract a var here
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.
m
what does it mean if it can't be found in javaThreads
? Does it mean it's simply not remapped from the original ID? Could we be reporting garbage data by using the original ID directly if remapping data is missing? e.g. eventThreadId = 9
, we can't find it in javaThreads
but the real thread ID was actually 117
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.
Yeah, potentially, if the orig. threadId somehow matches a different javaThreadId then we might map it to the wrong thread.
We could set it to a default thread id, like -1 if we can't resolve it. Or drop the event. WDYT?
} | ||
|
||
@Override | ||
public void visit(Event event, long value) { | ||
public void visit(Event event, long samples, long value) { |
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.
h
any exception here, e.g. when processing a single stack frame would cause the whole chunk to be thrown away if I understand correctly. Is that what we want? Can we only drop broken frames instead?
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.
How detailed do you think we should do that? just drop the whole event? i.e. catch any exception inside the visit method and just keep going?
@@ -8,6 +8,15 @@ public final class io/sentry/asyncprofiler/convert/JfrAsyncProfilerToSentryProfi | |||
public static fun convertFromFileStatic (Ljava/nio/file/Path;)Lio/sentry/protocol/profiling/SentryProfile; | |||
} | |||
|
|||
public final class io/sentry/asyncprofiler/convert/NonAggregatingEventCollector : io/sentry/asyncprofiler/vendor/asyncprofiler/jfr/event/EventCollector { |
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 related to this code.
l
do we needEventCollector
?
continue; | ||
} | ||
|
||
SentryStackFrame frame = createStackFrame(element); | ||
sentryProfile.getFrames().add(frame); | ||
frame.setNative(isNativeFrame(types[i])); |
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.
l
looks like this will always be false
|
||
File profileDir = new File(profilingTracesDirPath); | ||
|
||
if (!profileDir.canWrite() || !profileDir.exists()) { |
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.
l
can we split this up to provide a message that more easily helps pinpoint the problem?
private final @NotNull AtomicBoolean isClosed = new AtomicBoolean(false); | ||
private @NotNull SentryDate startProfileChunkTimestamp = new SentryNanotimeDate(); | ||
|
||
private @NotNull String filename = ""; | ||
|
||
private final @NotNull AsyncProfiler profiler; | ||
private @Nullable AsyncProfiler profiler; |
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.
l
an alternative to making this nullable could have been to handle the exception in AsyncProfilerContinuousProfilerProvider
and simply return a NoOpContinuousProfiler
. This would keep the code here simpler.
...y-async-profiler/src/main/java/io/sentry/asyncprofiler/profiling/JavaContinuousProfiler.java
Show resolved
Hide resolved
@@ -101,7 +130,6 @@ private boolean init() { | |||
return true; | |||
} | |||
|
|||
@SuppressWarnings("ReferenceEquality") | |||
@Override | |||
public void startProfiler( |
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.
m
should we catch here just in case something goes wrong?
@@ -0,0 +1,4 @@ | |||
# Vendored AsyncProfiler code for converting JFR Files | |||
- Vendored-in from commit fe1bc66d4b6181413847f6bbe5c0db805f3e9194 of repository: [email protected]:async-profiler/async-profiler.git |
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.
- Vendored-in from commit fe1bc66d4b6181413847f6bbe5c0db805f3e9194 of repository: git@github.com:async-profiler/async-profiler.git | |
- Vendored-in from commit https://github.com/async-profiler/async-profiler/tree/fe1bc66d4b6181413847f6bbe5c0db805f3e9194 |
// No need to check exact decimal places as this depends on JFR precision | ||
assertTrue( | ||
timestamp < System.currentTimeMillis() / 1000.0 + 360, | ||
"Timestamp should be recent", |
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.
I'm confused here. It can't be recent if we use a pre-existing JFR file.
Is the message wrong here?
Why do we need to add 360?
Is this intended for creating the file within the test and asserting it's at most 10s old? Then this test might fail on slow CI.
📜 Description
Follow-up of: #4576
Fixes comments from previous PRs
Fixes method timing sent to sentry by not aggregating JFR events
Fixes RateLimiter Handling for
profile_chunk
andprofile_chunk_ui
💡 Motivation and Context
💚 How did you test it?
📝 Checklist
sendDefaultPII
is enabled.🔮 Next steps