Skip to content

Commit

Permalink
Merge branch 'main' into otlp-reporter-pdata
Browse files Browse the repository at this point in the history
  • Loading branch information
dmathieu committed Dec 6, 2024
2 parents 1fe30b4 + 84cce0a commit bd69069
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 28 deletions.
1 change: 1 addition & 0 deletions host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Frame struct {

type Trace struct {
Comm string
Executable string
Frames []Frame
Hash TraceHash
KTime times.KTime
Expand Down
16 changes: 16 additions & 0 deletions processmanager/processinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ func (pm *ProcessManager) updatePidInformation(pid libpf.PID, m *Mapping) (bool,
if !ok {
// We don't have information for this pid, so we first need to
// allocate the embedded map for this process.
executable, _ := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
info = &processInfo{
executable: executable,
mappings: make(map[libpf.Address]*Mapping),
mappingsByFileID: make(map[host.FileID]map[libpf.Address]*Mapping),
tsdInfo: nil,
Expand Down Expand Up @@ -638,3 +640,17 @@ func (pm *ProcessManager) CleanupPIDs() {
log.Debugf("Cleaned up %d dead PIDs", len(deadPids))
}
}

// ExePathForPID returns the full executable path for given PID.
// If the PID is not tracked or belongs to a kernel worker,
// it returns the empty string.
func (pm *ProcessManager) ExePathForPID(pid libpf.PID) string {
var executable string

pm.mu.RLock()
defer pm.mu.RUnlock()
if procInfo, ok := pm.pidToProcessInfo[pid]; ok {
executable = procInfo.executable
}
return executable
}
2 changes: 2 additions & 0 deletions processmanager/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ func (m *Mapping) GetOnDiskFileIdentifier() util.OnDiskFileIdentifier {
// processInfo contains information about the executable mappings
// and Thread Specific Data of a process.
type processInfo struct {
// executable path retrieved from /proc/PID/exe
executable string
// executable mappings keyed by start address.
mappings map[libpf.Address]*Mapping
// executable mappings keyed by host file ID.
Expand Down
2 changes: 2 additions & 0 deletions reporter/internal/pdata/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ func (p *Pdata) setProfile(
semconv.ContainerIDKey, traceKey.ContainerID)
attrMgr.AppendOptionalString(sample.AttributeIndices(),
semconv.ThreadNameKey, traceKey.Comm)
attrMgr.AppendOptionalString(sample.AttributeIndices(),
semconv.ProcessExecutablePathKey, traceKey.Executable)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Analyze Go (amd64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Test (amd64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Analyze Go (arm64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Build integration test binaries (amd64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Lint (amd64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)) (typecheck)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Lint (amd64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable) (typecheck)

Check failure on line 181 in reporter/internal/pdata/generate.go

View workflow job for this annotation

GitHub Actions / Test (arm64)

traceKey.Executable undefined (type samples.TraceAndMetaKey has no field or method Executable, but does have unexported field executable)
attrMgr.AppendOptionalString(sample.AttributeIndices(),
semconv.ServiceNameKey, traceKey.ApmServiceName)
attrMgr.AppendInt(sample.AttributeIndices(),
Expand Down
3 changes: 3 additions & 0 deletions reporter/internal/samples/samples.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "go.opentelemetry.io/ebpf-profiler/libpf"
type TraceEventMeta struct {
Timestamp libpf.UnixTime64
Comm string
Executable string
APMServiceName string
PID, TID libpf.PID
CPU int
Expand Down Expand Up @@ -35,6 +36,8 @@ type TraceAndMetaKey struct {
// containerID is annotated based on PID information
ContainerID string
Pid int64
// Executable path is retrieved from /proc/PID/exe
executable string
// ExtraMeta stores extra meta info that may have been produced by a
// `SampleAttrProducer` instance. May be nil.
ExtraMeta any
Expand Down
1 change: 1 addition & 0 deletions reporter/otlp_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ func (r *OTLPReporter) ReportTraceEvent(trace *libpf.Trace, meta *TraceEventMeta
key := samples.TraceAndMetaKey{
Hash: trace.Hash,
Comm: meta.Comm,
Executable: meta.Executable,

Check failure on line 161 in reporter/otlp_reporter.go

View workflow job for this annotation

GitHub Actions / Lint (amd64)

unknown field Executable in struct literal of type samples.TraceAndMetaKey, but does have unexported executable (typecheck)
ApmServiceName: meta.APMServiceName,
ContainerID: containerID,
Pid: int64(meta.PID),
Expand Down
13 changes: 10 additions & 3 deletions tools/coredump/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,18 @@ with existing test cases.
In this variant we essentially make the kernel think that the target application
crashed, causing the kernel to save a coredump for us.

#### Setting the coredump filter
#### Setting the coredump filter (optional)

Coredumps normally contain only the anonymous and modified pages to save disk
space. For our test cases, we want a full process memory dump that also contains
the pages mapped into the process from the ELF files.
space. This is sufficient if the mapped in ELF files are available to the
`coredump` utility to be bundled. This is the case if you run
`./coredump new -core core` on the same machine where the core was generated,
or if you supply `-sysroot` as a prefix to find the correct files.

If the above is not possible, the testing infrastructure has limited support
to allow reading the ELF file data directly from the coredump. In this case
a full process memory dump that also contains the pages mapped into the process
from the ELF files is needed.

To get a full process memory dump one has to set the [`coredump_filter`][filter]
in advance by running:
Expand Down
54 changes: 30 additions & 24 deletions tools/coredump/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type newCmd struct {

// User-specified command line arguments.
coredumpPath string
sysroot string
pid uint64
name string
importThreadInfo string
Expand Down Expand Up @@ -110,6 +111,7 @@ func newNewCmd(store *modulestore.Store) *ffcli.Command {

set := flag.NewFlagSet("new", flag.ExitOnError)
set.StringVar(&args.coredumpPath, "core", "", "Path of the coredump to import")
set.StringVar(&args.sysroot, "sysroot", "", "Path for the coredump associated ELF files")
set.Uint64Var(&args.pid, "pid", 0, "PID to create a fresh coredump for")
set.StringVar(&args.name, "name", "", "Name for the test case [required]")
set.StringVar(&args.importThreadInfo, "import-thread-info", "", "If this flag is specified, "+
Expand Down Expand Up @@ -139,17 +141,19 @@ func (cmd *newCmd) exec(context.Context, []string) (err error) {
}

var corePath string
prefix := ""
prefix := cmd.sysroot
if cmd.coredumpPath != "" {
corePath = cmd.coredumpPath
} else {
// No path provided: create a new dump.
corePath, err = dumpCore(cmd.pid)
corePath, err = dumpCore(cmd.pid, cmd.noModuleBundling)
if err != nil {
return fmt.Errorf("failed to create coredump: %w", err)
}
defer os.Remove(corePath)
prefix = fmt.Sprintf("/proc/%d/root/", cmd.pid)
if prefix == "" {
prefix = fmt.Sprintf("/proc/%d/root/", cmd.pid)
}
}

core, err := newTrackedCoredump(corePath, prefix)
Expand Down Expand Up @@ -195,32 +199,34 @@ func (cmd *newCmd) exec(context.Context, []string) (err error) {
return nil
}

func dumpCore(pid uint64) (string, error) {
// Backup current coredump filter mask.
// https://man7.org/linux/man-pages/man5/core.5.html
coredumpFilterPath := fmt.Sprintf("/proc/%d/coredump_filter", pid)
prevMask, err := os.ReadFile(coredumpFilterPath)
if err != nil {
return "", fmt.Errorf("failed to read coredump filter: %w", err)
}
// Adjust coredump filter mask.
//nolint:gosec
err = os.WriteFile(coredumpFilterPath, []byte("0x3f"), 0o644)
if err != nil {
return "", fmt.Errorf("failed to write coredump filter: %w", err)
}
// Restore coredump filter mask upon leaving the function.
defer func() {
func dumpCore(pid uint64, noModuleBundling bool) (string, error) {
if noModuleBundling {
// Backup current coredump filter mask.
// https://man7.org/linux/man-pages/man5/core.5.html
coredumpFilterPath := fmt.Sprintf("/proc/%d/coredump_filter", pid)
prevMask, err := os.ReadFile(coredumpFilterPath)
if err != nil {
return "", fmt.Errorf("failed to read coredump filter: %w", err)
}
// Adjust coredump filter mask.
//nolint:gosec
err2 := os.WriteFile(coredumpFilterPath, prevMask, 0o644)
if err2 != nil {
log.Warnf("Failed to restore previous coredump filter: %v", err2)
err = os.WriteFile(coredumpFilterPath, []byte("0x3f"), 0o644)
if err != nil {
return "", fmt.Errorf("failed to write coredump filter: %w", err)
}
}()
// Restore coredump filter mask upon leaving the function.
defer func() {
//nolint:gosec
err2 := os.WriteFile(coredumpFilterPath, append([]byte("0x"), prevMask...), 0o644)
if err2 != nil {
log.Warnf("Failed to restore previous coredump filter: %v", err2)
}
}()
}

// `gcore` only accepts a path-prefix, not an exact path.
//nolint:gosec
err = exec.Command("gcore", "-o", gcorePathPrefix, strconv.FormatUint(pid, 10)).Run()
err := exec.Command("gcore", "-o", gcorePathPrefix, strconv.FormatUint(pid, 10)).Run()
if err != nil {
return "", fmt.Errorf("gcore failed: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions tracehandler/tracehandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func (m *traceHandler) HandleTrace(bpfTrace *host.Trace) {
TID: bpfTrace.TID,
APMServiceName: "", // filled in below
CPU: bpfTrace.CPU,
Executable: bpfTrace.Executable,
}

if !m.reporter.SupportsReportTraceEvent() {
Expand Down
4 changes: 3 additions & 1 deletion tracer/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,11 +856,13 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) *host.Trace {
panic("unexpected record size")
}

pid := libpf.PID(ptr.pid)
trace := &host.Trace{
Comm: C.GoString((*C.char)(unsafe.Pointer(&ptr.comm))),
Executable: t.processManager.ExePathForPID(pid),
APMTraceID: *(*libpf.APMTraceID)(unsafe.Pointer(&ptr.apm_trace_id)),
APMTransactionID: *(*libpf.APMTransactionID)(unsafe.Pointer(&ptr.apm_transaction_id)),
PID: libpf.PID(ptr.pid),
PID: pid,
TID: libpf.PID(ptr.tid),
KTime: times.KTime(ptr.ktime),
CPU: cpu,
Expand Down

0 comments on commit bd69069

Please sign in to comment.