Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ func parseTracers(tracers string) (interpreterconfig.Config, error) {
cfg.Go.Labels.Disabled = false
case "beam":
cfg.BEAM.Disabled = false
case "thread_context":
log.Warn("The thread context interpreter is a stub and does not do anything yet")
cfg.ThreadContext.Disabled = false
case "luajit":
log.Warn("The LuaJIT interpreter is incomplete and may not work properly")
cfg.LuaJIT.Disabled = false
Expand Down
56 changes: 31 additions & 25 deletions interpreter/interpreterconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,23 @@ import (
"go.opentelemetry.io/ebpf-profiler/interpreter/php"
"go.opentelemetry.io/ebpf-profiler/interpreter/python"
"go.opentelemetry.io/ebpf-profiler/interpreter/ruby"
"go.opentelemetry.io/ebpf-profiler/interpreter/threadcontext"
)

// Config holds configuration for all interpreters.
// By default all interpreters are enabled.
type Config struct {
Python python.Config `mapstructure:"python" json:"python"`
Perl perl.Config `mapstructure:"perl" json:"perl"`
PHP php.Config `mapstructure:"php" json:"php"`
Hotspot hotspot.Config `mapstructure:"hotspot" json:"hotspot"`
Ruby ruby.Config `mapstructure:"ruby" json:"ruby"`
V8 nodev8.Config `mapstructure:"v8" json:"v8"`
Dotnet dotnet.Config `mapstructure:"dotnet" json:"dotnet"`
Go golang.Config `mapstructure:"go" json:"go"`
BEAM beam.Config `mapstructure:"beam" json:"beam"`
LuaJIT luajit.Config `mapstructure:"luajit" json:"luajit"`
Python python.Config `mapstructure:"python" json:"python"`
Perl perl.Config `mapstructure:"perl" json:"perl"`
PHP php.Config `mapstructure:"php" json:"php"`
Hotspot hotspot.Config `mapstructure:"hotspot" json:"hotspot"`
Ruby ruby.Config `mapstructure:"ruby" json:"ruby"`
V8 nodev8.Config `mapstructure:"v8" json:"v8"`
Dotnet dotnet.Config `mapstructure:"dotnet" json:"dotnet"`
Go golang.Config `mapstructure:"go" json:"go"`
BEAM beam.Config `mapstructure:"beam" json:"beam"`
LuaJIT luajit.Config `mapstructure:"luajit" json:"luajit"`
ThreadContext threadcontext.Config `mapstructure:"thread_context" json:"thread_context"`
}

// AllInterpreters returns a Config with all interpreters enabled.
Expand All @@ -41,22 +43,23 @@ func AllInterpreters() Config { return Config{} }
func NoInterpreters() Config {
disabled := interpreter.BaseConfig{Disabled: true}
return Config{
Python: python.Config{BaseConfig: disabled},
Perl: perl.Config{BaseConfig: disabled},
PHP: php.Config{BaseConfig: disabled},
Hotspot: hotspot.Config{BaseConfig: disabled},
Ruby: ruby.Config{BaseConfig: disabled},
V8: nodev8.Config{BaseConfig: disabled},
Dotnet: dotnet.Config{BaseConfig: disabled},
Go: golang.Config{BaseConfig: disabled},
BEAM: beam.Config{BaseConfig: disabled},
LuaJIT: luajit.Config{BaseConfig: disabled},
Python: python.Config{BaseConfig: disabled},
Perl: perl.Config{BaseConfig: disabled},
PHP: php.Config{BaseConfig: disabled},
Hotspot: hotspot.Config{BaseConfig: disabled},
Ruby: ruby.Config{BaseConfig: disabled},
V8: nodev8.Config{BaseConfig: disabled},
Dotnet: dotnet.Config{BaseConfig: disabled},
Go: golang.Config{BaseConfig: disabled},
BEAM: beam.Config{BaseConfig: disabled},
LuaJIT: luajit.Config{BaseConfig: disabled},
ThreadContext: threadcontext.Config{BaseConfig: disabled},
}
}

// Loaders returns active loaders for all enabled interpreters.
func (cfg *Config) Loaders() []interpreter.Loader {
loaders := make([]interpreter.Loader, 0, 11)
loaders := make([]interpreter.Loader, 0, 12)
if !cfg.Perl.IsDisabled() {
loaders = append(loaders, perl.GetLoader(cfg.Perl))
}
Expand Down Expand Up @@ -88,6 +91,9 @@ func (cfg *Config) Loaders() []interpreter.Loader {
if !cfg.Go.IsDisabled() {
loaders = append(loaders, golang.GetLoader(cfg.Go))
}
if !cfg.ThreadContext.IsDisabled() {
loaders = append(loaders, threadcontext.GetLoader(cfg.ThreadContext))
}
return loaders
}

Expand All @@ -113,10 +119,10 @@ func (cfg *Config) IsMapEnabled(mapName string) bool {
return !cfg.BEAM.IsDisabled()
case luajit.BPFMapName:
return !cfg.LuaJIT.IsDisabled()
case golang.BPFMapName, apmint.BPFMapName:
// go_procs is read from collect_trace (preloaded into the PerCPURecord)
// and apm_int_procs from unwind_stop, so both must always be loaded
// regardless of interpreter configuration.
case golang.BPFMapName, apmint.BPFMapName, threadcontext.BPFMapName:
// go_procs is read from collect_trace (preloaded into the PerCPURecord),
// and apm_int_procs and thread_context_procs from unwind_stop, so all
// three must always be loaded regardless of interpreter configuration.
return true
default:
return true // Not an interpreter map, so it should be loaded
Expand Down
14 changes: 14 additions & 0 deletions interpreter/threadcontext/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package threadcontext // import "go.opentelemetry.io/ebpf-profiler/interpreter/threadcontext"

import "go.opentelemetry.io/ebpf-profiler/interpreter"

const BPFMapName = "thread_context_procs"

type Config struct {
interpreter.BaseConfig `mapstructure:",squash"`
}

var _ interpreter.Config = Config{}
47 changes: 47 additions & 0 deletions interpreter/threadcontext/threadcontext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Boilerplate stubs for the thread context implementation.
package threadcontext // import "go.opentelemetry.io/ebpf-profiler/interpreter/threadcontext"

import (
"go.opentelemetry.io/ebpf-profiler/interpreter"
"go.opentelemetry.io/ebpf-profiler/libpf"
"go.opentelemetry.io/ebpf-profiler/remotememory"
)

func GetLoader(_ Config) interpreter.Loader {
return interpreter.NewLoader(loader, []interpreter.InterpreterResource{
{MapName: BPFMapName},
})
}

func loader(_ interpreter.EbpfHandler, _ *interpreter.LoaderInfo) (interpreter.Data, error) {
return nil, nil
}

type threadcontextData struct{}

var _ interpreter.Data = &threadcontextData{}

func (d *threadcontextData) String() string {
return "Native thread labels"
}

func (d *threadcontextData) Attach(_ interpreter.EbpfHandler, _ libpf.PID,
_ libpf.Address, _ remotememory.RemoteMemory,
) (interpreter.Instance, error) {
return &Instance{}, nil
}

func (d *threadcontextData) Unload(_ interpreter.EbpfHandler) {}

type Instance struct {
interpreter.InstanceStubs
}

var _ interpreter.Instance = &Instance{}

func (i *Instance) Detach(_ interpreter.EbpfHandler, _ libpf.PID) error {
return nil
}
30 changes: 17 additions & 13 deletions libpf/interpretertype.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const (

// APMInt identifies the pseudo-interpreter for the APM integration.
APMInt InterpreterType = 0x100

// ThreadContext identifies the pseudo-interpreter for thread context support.
ThreadContext InterpreterType = 0x101
)

// Frame converts the interpreter type into the corresponding frame type.
Expand All @@ -61,19 +64,20 @@ var interpreterTypeToString = map[InterpreterType]string{
UnknownInterp: "unknown",
PHP: "php",
// OTel SemConv does not differentiate between jitted code and interpreted code.
PHPJIT: "php",
Python: "cpython",
Native: "native",
Kernel: "kernel",
HotSpot: "jvm",
Ruby: "ruby",
Perl: "perl",
V8: "v8js",
Dotnet: "dotnet",
BEAM: "beam",
APMInt: "apm-integration",
LuaJIT: "luajit",
Go: "go",
PHPJIT: "php",
Python: "cpython",
Native: "native",
Kernel: "kernel",
HotSpot: "jvm",
Ruby: "ruby",
Perl: "perl",
V8: "v8js",
Dotnet: "dotnet",
BEAM: "beam",
APMInt: "apm-integration",
ThreadContext: "thread-context",
LuaJIT: "luajit",
Go: "go",
}

var stringToInterpreterType = make(map[string]InterpreterType, len(interpreterTypeToString))
Expand Down
14 changes: 13 additions & 1 deletion metrics/ids.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions metrics/metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -2230,5 +2230,33 @@
"name": "UnwindGoAsmcgocallUnwindFailure",
"field": "bpf.go.errors.asmcgocall_unwind_failure",
"id": 305
},
{
"description": "Number of failures to read the thread context buffer pointer out of TLS",
"type": "counter",
"name": "UnwindThreadContextErrReadTlsPtr",
"field": "bpf.threadctx.errors.read_tls_ptr",
"id": 306
},
{
"description": "Number of failures to read the thread context buffer, header or payload",
"type": "counter",
"name": "UnwindThreadContextErrReadThreadCtxBuf",
"field": "bpf.threadctx.errors.read_thread_ctx_buf",
"id": 307
},
{
"description": "Number of successful reads of thread context info",
"type": "counter",
"name": "UnwindThreadContextReadSuccesses",
"field": "bpf.threadctx.read_successes",
"id": 308
},
{
"description": "Number of thread context attribute payloads truncated to fit the buffer",
"type": "counter",
"name": "UnwindThreadContextAttrsTruncated",
"field": "bpf.threadctx.attrs_truncated",
"id": 309
}
]
3 changes: 3 additions & 0 deletions processmanager/ebpf/ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type ebpfMapsImpl struct {
LuaJitProcs *cebpf.Map `name:"luajit_procs"`
ApmIntProcs *cebpf.Map `name:"apm_int_procs"`
GoProcs *cebpf.Map `name:"go_procs"`
ThreadContextProcs *cebpf.Map `name:"thread_context_procs"`

// Stackdelta and process related eBPF maps
ExeIDToStackDeltaMaps []*cebpf.Map
Expand Down Expand Up @@ -172,6 +173,8 @@ func (impl *ebpfMapsImpl) getInterpreterTypeMap(typ libpf.InterpreterType) (*ceb
return impl.GoProcs, nil
case libpf.LuaJIT:
return impl.LuaJitProcs, nil
case libpf.ThreadContext:
return impl.ThreadContextProcs, nil
default:
return nil, fmt.Errorf("type %d is not (yet) supported", typ)
}
Expand Down
1 change: 1 addition & 0 deletions support/ebpf/extmaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern struct inhibit_events_t inhibit_events;
extern struct interpreter_offsets_t interpreter_offsets;
extern struct trace_events_t trace_events;
extern struct go_procs_t go_procs;
extern struct thread_context_procs_t thread_context_procs;

extern struct exe_id_to_8_stack_deltas_t exe_id_to_8_stack_deltas;
extern struct exe_id_to_9_stack_deltas_t exe_id_to_9_stack_deltas;
Expand Down
24 changes: 23 additions & 1 deletion support/ebpf/interpreter_dispatcher.ebpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ struct apm_int_procs_t {
__uint(max_entries, 128);
} apm_int_procs SEC(".maps");

struct thread_context_procs_t {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, pid_t);
__type(value, ThreadContextProcInfo);
__uint(max_entries, 1024);
} thread_context_procs SEC(".maps");

// filter_error_frames is set during load time.
BPF_RODATA_VAR(bool, filter_error_frames, false)

Expand Down Expand Up @@ -249,6 +256,18 @@ static EBPF_INLINE void maybe_add_apm_info(Trace *trace)
corr_buf.trace_flags);
}

// Stub: only looks the process up, so thread_context_procs stays loaded
// regardless of whether the tracer is enabled. Reading and decoding the
// thread context lands in a later change.
static EBPF_INLINE void maybe_add_thread_context_info(Trace *trace)
{
u32 pid = trace->pid;
ThreadContextProcInfo *proc = bpf_map_lookup_elem(&thread_context_procs, &pid);
if (!proc) {
return;
}
}

// unwind_stop is the tail call destination for PROG_UNWIND_STOP.
static EBPF_INLINE int unwind_stop(struct pt_regs *ctx)
{
Expand Down Expand Up @@ -314,9 +333,12 @@ static EBPF_INLINE int unwind_stop(struct pt_regs *ctx)
}
// TEMPORARY HACK END

// Must be last since it may not return (it will call send_trace).
// Does not return once it dispatches, so anything below runs only when the
// Go path did not fill custom labels.
maybe_add_go_custom_labels(ctx, record);

maybe_add_thread_context_info(trace);

send_trace(ctx, trace);

return 0;
Expand Down
Binary file modified support/ebpf/tracer.ebpf.amd64
Binary file not shown.
Binary file modified support/ebpf/tracer.ebpf.arm64
Binary file not shown.
34 changes: 34 additions & 0 deletions support/ebpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,18 @@ enum {
// number of Go asmcgocall unwind failures
metricID_UnwindGoAsmcgocallUnwindFailure,

// number of failures to read the thread context buffer pointer out of TLS
metricID_UnwindThreadContextErrReadTlsPtr,

// number of failures to read the thread context buffer, header or payload
metricID_UnwindThreadContextErrReadThreadCtxBuf,

// number of successful reads of thread context info
metricID_UnwindThreadContextReadSuccesses,

// number of thread context attribute payloads truncated to fit the buffer
metricID_UnwindThreadContextAttrsTruncated,

//
// Metric IDs above are for counters (cumulative values)
//
Expand Down Expand Up @@ -432,6 +444,22 @@ typedef struct DTVInfo {
u8 multiplier;
} DTVInfo;

// TLSVarInfo locates a thread-local variable at unwind time, covering both
// static and dynamic TLS.
typedef struct TLSVarInfo {
// TP-relative when module_id is 0, else within that module's TLS block.
// Signed because variant II puts the static block below the thread pointer.
s32 tls_offset;
// 0 for static TLS, and the only static/dynamic discriminant.
u16 module_id;
// Unused when module_id == 0, and empty until libc introspection supplies it.
DTVInfo dtv_info;
// Needed because a zeroed TLSVarInfo is otherwise a valid static descriptor:
// aarch64 musl gives tls_offset 0 to a library whose executable has no
// PT_TLS. Dynamic TLS stays false until dtv_info arrives.
bool resolved;
} TLSVarInfo;

// DotnetProcInfo is a container for the data needed to build stack trace for a dotnet process.
typedef struct DotnetProcInfo {
u32 version;
Expand Down Expand Up @@ -1112,4 +1140,10 @@ typedef struct ApmIntProcInfo {
u64 tls_offset;
} ApmIntProcInfo;

// ThreadContextProcInfo is a container for the data needed to locate the
// thread context TLS variable of a process.
typedef struct ThreadContextProcInfo {
TLSVarInfo tls;
} ThreadContextProcInfo;

#endif // OPTI_TYPES_H
3 changes: 3 additions & 0 deletions support/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ go fmt .
# Set correct package path
sed -i 's/^package support$/package support \/\/ import "go.opentelemetry.io\/ebpf-profiler\/support"/' types_gen.go

# TLSVarInfo fields stay private: the constructors enforce the module_id/resolved invariants.
sed -i '/^type TLSVarInfo struct {$/,/^}$/ s/^\t\([A-Z]\)/\t\l\1/' types_gen.go

if ! diff types_gen.go types.go; then
echo "Auto generated and existing code differ, please review and update support/types.go"
exit 1
Expand Down
Loading
Loading