Skip to content

Conversation

lbloder
Copy link
Collaborator

@lbloder lbloder commented Aug 29, 2025

📜 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 and profile_chunk_ui

💡 Motivation and Context

💚 How did you test it?

📝 Checklist

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

  • Add option to filter threads in converter to reduce payload and data that needs to be handled in the backend

Copy link
Contributor

🚨 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/src/main/java/io/sentry/transport/RateLimiter.java

Copy link
Contributor

github-actions bot commented Aug 29, 2025

Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

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 #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 524a32a

Copy link
Contributor

🚨 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/src/main/java/io/sentry/transport/RateLimiter.java

Copy link
Contributor

github-actions bot commented Aug 29, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 415.48 ms 485.28 ms 69.80 ms
Size 1.58 MiB 2.09 MiB 521.84 KiB

Previous results on branch: feat/continuous-profiling-03

Startup times

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

Copy link
Contributor

github-actions bot commented Sep 5, 2025

🚨 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/src/main/java/io/sentry/transport/RateLimiter.java

@lbloder lbloder mentioned this pull request Sep 8, 2025
9 tasks
Copy link
Contributor

github-actions bot commented Sep 8, 2025

🚨 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/src/main/java/io/sentry/transport/RateLimiter.java

@lbloder lbloder changed the title add readme and info about commit of the source repository Profiling - Deduplication and cleanup Sep 8, 2025
@lbloder lbloder mentioned this pull request Sep 8, 2025
10 tasks
@lbloder
Copy link
Collaborator Author

lbloder commented Sep 8, 2025

@sentry review

@lbloder
Copy link
Collaborator Author

lbloder commented Sep 8, 2025

cursor review

Copy link

@cursor cursor bot left a 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!


Comment on lines 369 to +427
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);
}
Copy link

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.

Comment on lines 20 to +21
import java.util.List;
import java.util.Map;
Copy link

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.

Copy link
Contributor

github-actions bot commented Sep 8, 2025

🚨 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/src/main/java/io/sentry/transport/RateLimiter.java

Copy link
Member

@adinauer adinauer left a 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
Copy link
Member

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

Copy link
Member

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

Copy link
Collaborator Author

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) {
Copy link
Member

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?

Copy link
Collaborator Author

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 {
Copy link
Member

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 need EventCollector?

continue;
}

SentryStackFrame frame = createStackFrame(element);
sentryProfile.getFrames().add(frame);
frame.setNative(isNativeFrame(types[i]));
Copy link
Member

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()) {
Copy link
Member

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;
Copy link
Member

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.

@@ -101,7 +130,6 @@ private boolean init() {
return true;
}

@SuppressWarnings("ReferenceEquality")
@Override
public void startProfiler(
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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",
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants