feat: record stacktrace for objects allocated in stack#76
Conversation
|
Hi @ppzqh and @jayantxie. Could you please review this pull request when you have time? I hope this PR resolves issue #65. Please let me know if there is anything that can be improved. Thanks for your time and effort in maintaining this tool! |
|
Hi @Lslightly! Thanks for your work. I will review this. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #76 +/- ##
==========================================
+ Coverage 2.70% 61.83% +59.13%
==========================================
Files 15 16 +1
Lines 2221 2319 +98
==========================================
+ Hits 60 1434 +1374
+ Misses 2155 726 -1429
- Partials 6 159 +153
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Hi, @ppzqh. I fixed errors of tests. Do I need to add additional tests to increase the coverage rate? It seems that other merged PRs also have "Patch coverage" problem. |
|
Yes, you need to add a |
remove G<id>, the bottom of stacktrace
| } | ||
| // write location | ||
| start := b.pb.startMessage() | ||
| b.pb.uint64Opt(tagLocation_ID, idx) |
There was a problem hiding this comment.
Location ID is keyed by function name, while Address and Line are PC-specific. Multiple PCs in the same function may therefore reuse incorrect source information. Since we only need function-level stacks, we can remove the PC/file/line metadata.
There was a problem hiding this comment.
In 8ae7f8e, I simplify the stacktrace to function-level stacks and remove the PC/file/line metadata.
If delve provides correct PC/file/line, then adding them allows user to get correct pprof source view. I try to keep my opinion to add them, but I can't figure out the use case for source view now. File/line seems meaningless for a temporary memory snapshot and function is enough.
|
|
||
| const StackTraceObjSplitLine string = "stk_obj_split" | ||
|
|
||
| func createStackTrace(b *profileBuilder, sf []proc.Stackframe, t *proc.Target, g *proc.G) *pprofIndex { |
There was a problem hiding this comment.
Since only function-level stacks are needed, createStackTrace should only append interned function names to pprofIndex. Keep Location and Function emission in flush, where stringIndex already guarantees deduplication. Then funcNameStrIdxSet is also not needed.
There was a problem hiding this comment.
Besides, stack frames and the separator are attribution context, not object-reference edges. Using pushHead makes them consume maxRefDepth and can skip local roots on deep stacks. We might need another helper function of pprofIndex to handle this case.
There was a problem hiding this comment.
Keep Location and Function emission in flush, where stringIndex already guarantees deduplication. Then funcNameStrIdxSet is also not needed.
8ae7f8e keeps Location and Function emission in flush and removes logic to add PC/file/line in Location.
funcNameStrIdxSet has been removed.
We might need another helper function of pprofIndex to handle this case.
newHeadWithDepth helper function is added and depth of stacktrace node's pprofIndex is set to -1.
Is it better to set the depth to negative stack depth? Depth of ReferenceStackBoundary is 0, then -1, -2, ... The change made to object reference is that depth of objects referenced by local variables increase by 1.
| l.Name = sf[i].Current.Fn.Name + "." + l.Name | ||
| rv := ToReferenceVariable(l) | ||
| s.findRef(rv, nil) | ||
| s.findRef(rv, createStackTrace(s.pb, sf[i:], t, gr)) |
There was a problem hiding this comment.
createStackTrace is called for every local variable, repeatedly rebuilding the same stack suffix. We can precompute immutable prefixes once per goroutine, build them in reverse to share suffix nodes, and reuse prefixes[i] for all locals in frame i
There was a problem hiding this comment.
Thanks. That's true. We don't need to use pushHead repeatedly for each local variable to find pprofIndex previously built. prefixes[i] is much faster.
There was a problem hiding this comment.
Done in 8ae7f8e with initStackTrace and pushReferenceStackBoundary
| depth int | ||
| } | ||
|
|
||
| const StackTraceObjSplitLine string = "stk_obj_split" |
There was a problem hiding this comment.
rename to a clearer one, for example:
const ReferenceStackSeparator string = "[goref:reference-stack-boundary]"
1. remove file/line/PC metadata. 2. init stacktrace for each goroutine and reuse the stacktrace

What type of PR is this?
feat
Check the PR title.
(Optional) Translate the PR title into Chinese.
(Optional) More detailed description for this PR(en: English/zh: Chinese).
en: For local variables in stack, the stacktrace is recorded as
pprofIndexzh(optional): 对于栈上局部变量,在pprofIndex中记录栈帧。
Take test/testdata/manygo/main.go as an example. The flamegraph is as follows. There are 3 groups of goroutines whose members share same stacktrace.
Hope that it supplements goroutine leak profile.
(Optional) Which issue(s) this PR fixes:
Fixes #65
(optional) The PR that updates user documentation: