-
Notifications
You must be signed in to change notification settings - Fork 98
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
Update StructFieldConst
to find missing offsets at runtime
#1593
Conversation
Unify the opening of the target process exe file as a field.
If the offset index does not contain an entry for the desired struct field, use the target process DWARF data to determine the offset.
Used to temporarily test the functionality. Should be reverted before merge.
76c1628
to
0c18ff3
Compare
This reverts commit 0c18ff3.
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.
This is awesome
DWARF data caching analysisI wrote a benchmark to evaluate the overhead of reading the DWARF data: package dwarfsize
import (
"debug/dwarf"
"debug/elf"
"reflect"
"testing"
"unsafe"
)
func Benchmark(b *testing.B) {
b.Run("otelcontribcol", benchmark("./bin/otelcontribcol_linux_amd64"))
b.Run("autosdk", benchmark("./bin/autosdk"))
}
func benchmark(path string) func(*testing.B) {
return func(b *testing.B) {
f, err := elf.Open(path)
if err != nil {
b.Fatalf("failed to open ELF: %s", path)
}
defer f.Close()
data, err := f.DWARF()
if err != nil {
b.Fatalf("failed to read DWARF symbols: %s", err)
}
if data == nil {
b.Fatalf("empty DWARF data")
}
size := dwarfSize(*data)
b.Logf("DWARF data size: %d bytes", size)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
data, err = f.DWARF()
if err != nil {
b.Fatal(err)
}
}
_ = data
}
}
func dwarfSize(data dwarf.Data) uintptr {
size := unsafe.Sizeof(data)
v := reflect.ValueOf(data)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
switch field.Type().Kind() {
case reflect.Array, reflect.Chan, reflect.Slice:
size += uintptr(field.Cap()) * field.Type().Size()
case reflect.Map, reflect.String:
size += uintptr(field.Len()) * field.Type().Size()
default:
}
}
return size
} I evaluated this against the
Results
The logged dwarf data size:
Take-awayIf my analysis is correct, it looks like this can allocate up to a few GB for binaries with large DWARF data and also take a few seconds to process that data. I plan to remove the caching based on this analysis. It seems more reasonable to have the auto-instrumentation take a longer time to start in the failure-case that we do not know an offset or two than it is to have the resident memory of the auto-instrumentation be several GB for its lifetime. We can always readdress this in the future if needed. |
This reverts commit c5cb54b. Analysis has show that this caching could cause large allocations of memory to be held for the lifetime of the Instrumentation. This is removed to avoid that.
@MrAlias Thanks for doing this benchmark, it's really interesting. |
If the offset index does not contain an entry for the desired struct field, use the target process DWARF data to determine the offset.
Testing
End-to-end testing was done by updating the
autosdk
e2e test. All dependencies were bumped to commit hash versions:0c18ff3#diff-14bffadf6f47d1ef427c47d4425fca90398e7b4c8d96f0e58f1a371833d837ebR6-R8
It was then tested both locally and on our CI system.
Locally the logs were verified to show this was working:
The update was then reverted given it will be managed by renovate.