Skip to content

Implement method call stack tracing #150

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

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from

Conversation

sohyun-ku
Copy link
Contributor

@sohyun-ku sohyun-ku commented Apr 21, 2025

  • resolved: implement method call stack #109

  • Call Stack Tracing with ConcurrentHashMap + Deque

    • To manage call stacks per thread, we used the structure ConcurrentHashMap<Long, ArrayDeque<String>>.
    • Each thread maintains its own call stack (Deque), and method entries and exits are tracked via push and pop operations respectively.
    • The Deque is used as a stack to accurately capture the order and depth of method calls.
  • Caller-Callee Based Lightweight Call Structure

    • Instead of storing the entire method call stack, each call holds a reference to its direct caller only.
    • This design retains the hierarchical relationship between method calls while significantly reducing memory overhead by avoiding redundant stack information.
    • It allows for a clear understanding of “who called whom” at the direct (1-depth) level,
      and can be effectively used for incremental exploration or future enhancements such as call relationship visualization.
  • Benchmark Results (ns/op)

    • The benchmarking tests were conducted on a local machine with an Apple M1 Pro, 32GB RAM. Please note that results may vary depending on the environment.
    • Since enabling CallStackTraceMode introduces some overhead, the feature is implemented as optional, allowing it to be turned on only when detailed call tracing is needed.
    Java version CallStackTraceMode False CallStackTraceMode True
    Java 8 14.056 ± 0.235 34.004 ± 0.403
    Java 11 12.933 ± 0.247 27.244 ± 0.344
    Java 17 12.831 ± 0.340 26.625 ± 0.248
    Java21 12.502 ± 0.104 26.408 ± 3.061
  • Snapshot: Caller-Callee Relationship Example

스크린샷 2025-04-21 오후 4 53 52

@sohyun-ku sohyun-ku self-assigned this Apr 21, 2025
@sohyun-ku sohyun-ku requested review from junoyoon and a team as code owners April 21, 2025 08:19
Copy link

github-actions bot commented Apr 21, 2025

Scavenger Test Results

178 files  178 suites   3m 28s ⏱️
304 tests 297 ✅ 7 💤 0 ❌
326 runs  319 ✅ 7 💤 0 ❌

Results for commit cf1d43d.

♻️ This comment has been updated with latest results.

Copy link
Contributor

@taeyeon-Kim taeyeon-Kim left a comment

Choose a reason for hiding this comment

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

Sorry for the late review. I only checked the agent and api modules first.

  • Is it possible to see the call stack tree maybe?
  • Also, could you please change the vitess configuration file.

@@ -81,7 +88,9 @@ public class SchedulerTest {
public void setUp() throws Exception {
lenient().when(codeBaseScannerMock.scan())
.thenReturn(new CodeBase(List.of(Method.createTestMethod()), "fingerprint"));
sut = new Scheduler(invocationRegistry, new Config(new Properties()), publisher, codeBaseScannerMock);
Properties properties = new Properties();
properties.setProperty("callStackTraceMode", "true");
Copy link
Contributor

Choose a reason for hiding this comment

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

To make it more clear, it also needs to be tested when callStackTraceMode is false

invokedAtMillis: Long?,
): String =
"""
SELECT
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you check query performance?

EXPLAIN FORMAT=VITESS {query}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the late reply :(
Here is the Vitess query plan

+----------+-------------------+----------+-------------+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| operator | variant           | keyspace | destination | tabletType | query                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+----------+-------------------+----------+-------------+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Route    | SelectEqualUnique | main     |             | UNKNOWN    | select caller_methods.signature as callerSignature from call_stacks join methods as callee_methods on call_stacks.customerId = callee_methods.customerId and call_stacks.signatureHash = callee_methods.signatureHash join methods as caller_methods on call_stacks.customerId = caller_methods.customerId and call_stacks.callerSignatureHash = caller_methods.signatureHash where callee_methods.signature = '${PATH}' and call_stacks.customerId = 11000002003 and call_stacks.applicationId in (11003142347) and call_stacks.environmentId in (11003142346) and (1751941260000 is null or call_stacks.invokedAtMillis >= 1751941260000) |
+----------+-------------------+----------+-------------+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

@@ -59,7 +59,7 @@ class MethodDao(
return 0
}
return update(
sql.deleteAllMethodsAndInvocations(),
sql.deleteAllMethods(),
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@sohyun-ku
Copy link
Contributor Author

@taeyeon-Kim

Is it possible to see the call stack tree maybe?

In the current PR, the implementation is based on a one-way caller-callee relationship. However, I expect that in future feature expansions, it will be possible to construct the full call tree by recursively tracing the call path in reverse from a specific callee.
I considered various ways to model the call structure, but ultimately chose this simple caller-callee approach for its simplicity and ease of maintenance.
If you see any limitations in the current structure or believe another approach might be more appropriate, I’d appreciate your feedback.

@taeyeon-Kim
Copy link
Contributor

@sohyun-ku This is a great idea, and for an early version, this is great!!
Can I ask you to check one thing, if it is possible to extend this, for example, to make it possible to draw a tree from Caller to Root Caller.

@sohyun-ku
Copy link
Contributor Author

@taeyeon-Kim

I think it is technically possible to build a tree from a specific callee up to the root
(e.g., by traversing the reversed graph recursively to explore all possible call paths).

However, the stored call data represents multiple call records across the application rather than a single call flow.
Therefore, the constructed tree resembles a combination of all possible call relationships rather than an actual call stack.

Additionally, since the call graph may contain cycles and paths can get deep,
it’s important to apply constraints such as a visited set to avoid duplicates and depth limits to prevent excessive recursive traversal.

In summary, while extending the tree up to the root is feasible, please note that this does not exactly reproduce a single actual call flow that triggered the callee.

Due to the necessity of minimizing the performance impact on the agent,
these constraints inevitably arose to reduce performance and memory overhead during tree construction.

Copy link
Contributor

@taeyeon-Kim taeyeon-Kim left a comment

Choose a reason for hiding this comment

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

🔥 👍 👍 👍 👍

@taeyeon-Kim
Copy link
Contributor

@taeyeon-Kim

I think it is technically possible to build a tree from a specific callee up to the root (e.g., by traversing the reversed graph recursively to explore all possible call paths).

However, the stored call data represents multiple call records across the application rather than a single call flow. Therefore, the constructed tree resembles a combination of all possible call relationships rather than an actual call stack.

Additionally, since the call graph may contain cycles and paths can get deep, it’s important to apply constraints such as a visited set to avoid duplicates and depth limits to prevent excessive recursive traversal.

In summary, while extending the tree up to the root is feasible, please note that this does not exactly reproduce a single actual call flow that triggered the callee.

Due to the necessity of minimizing the performance impact on the agent, these constraints inevitably arose to reduce performance and memory overhead during tree construction.

@sohyun-ku yes I understand, thanks for the clarification. 😄

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.

implement method call stack
2 participants