diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1e3a4df..4906d8e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http ### Added - Add `WithServiceName` config option for instrumentation. ([#353](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/353)) +- Add `WithPID` config option for instrumentation. ([#355](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/355)) ### Changed diff --git a/Makefile b/Makefile index c43492b1d..68fe36921 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,10 @@ generate: docker-generate: docker run --rm -v $(shell pwd):/app golang:1.20 /bin/sh -c "apt-get update && apt-get install -y clang llvm libbpf-dev && cd ../app && make generate" +.PHONY: docker-test +docker-test: + docker run --rm -v $(shell pwd):/app golang:1.20 /bin/sh -c "apt-get update && apt-get install -y clang llvm libbpf-dev && cd ../app && make test" + .PHONY: go-mod-tidy go-mod-tidy: $(ALL_GO_MOD_DIRS:%=go-mod-tidy/%) go-mod-tidy/%: DIR=$* diff --git a/instConfig_test.go b/instConfig_test.go index 24379c885..c520c5258 100644 --- a/instConfig_test.go +++ b/instConfig_test.go @@ -53,3 +53,18 @@ func TestWithServiceName(t *testing.T) { c = newInstConfig([]InstrumentationOption{WithServiceName((testServiceName))}) assert.Equal(t, envServiceName, c.serviceName) } + +func TestWithPID(t *testing.T) { + // Current PID + currPID := os.Getpid() + c := newInstConfig([]InstrumentationOption{WithPID(currPID)}) + currExe, err := os.Executable() + if err != nil { + t.Error(err) + } + assert.Equal(t, currPID, c.target.Pid) + + // PID should override valid target exe + c = newInstConfig([]InstrumentationOption{WithPID(currPID), WithTarget(currExe)}) + assert.Equal(t, currPID, c.target.Pid) +} diff --git a/instrumentation.go b/instrumentation.go index c30d4791f..5e9c1eb11 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -50,8 +50,8 @@ type Instrumentation struct { manager *instrumentors.Manager } -// Error message returned when instrumentation is launched without a target -// binary. +// Error message returned when instrumentation is launched without a valid target +// binary or pid. var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider setting the %s environment variable pointing to the target binary to instrument", envTargetExeKey) // NewInstrumentation returns a new [Instrumentation] configured with the @@ -83,6 +83,14 @@ func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) mngr.Close() return nil, err } + + if log.Logger.IsZero() { + err := log.Init() + if err != nil { + return nil, err + } + } + log.Logger.V(0).Info( "target process analysis completed", "pid", td.PID, @@ -122,9 +130,11 @@ type instConfig struct { } func newInstConfig(opts []InstrumentationOption) instConfig { - var c instConfig + c := instConfig{target: &process.TargetArgs{}} for _, opt := range opts { - c = opt.apply(c) + if opt != nil { + c = opt.apply(c) + } } c = c.applyEnv() return c @@ -132,7 +142,7 @@ func newInstConfig(opts []InstrumentationOption) instConfig { func (c instConfig) applyEnv() instConfig { if v, ok := os.LookupEnv(envTargetExeKey); ok { - c.target = &process.TargetArgs{ExePath: v} + c.target.ExePath = v } if v, ok := os.LookupEnv(envServiceNameKey); ok { c.serviceName = v @@ -146,7 +156,7 @@ func (c instConfig) applyEnv() instConfig { } func (c instConfig) setDefualtServiceName() instConfig { - if c.target != nil { + if c.target.ExePath != "" { c.serviceName = fmt.Sprintf("%s:%s", serviceNameDefault, filepath.Base(c.target.ExePath)) } else { c.serviceName = serviceNameDefault @@ -190,6 +200,9 @@ func (o fnOpt) apply(c instConfig) instConfig { return o(c) } // WithTarget returns an [InstrumentationOption] defining the target binary for // [Instrumentation] that is being executed at the provided path. // +// This option conflicts with [WithPID]. If both are used, [WithPID] will take +// precedence and be used. +// // If multiple of these options are provided to an [Instrumentation], the last // one will be used. // @@ -197,7 +210,7 @@ func (o fnOpt) apply(c instConfig) instConfig { return o(c) } // passed here. func WithTarget(path string) InstrumentationOption { return fnOpt(func(c instConfig) instConfig { - c.target = &process.TargetArgs{ExePath: path} + c.target.ExePath = path return c }) } @@ -215,3 +228,19 @@ func WithServiceName(serviceName string) InstrumentationOption { return c }) } + +// WithPID returns an [InstrumentationOption] corresponding to the executable +// used by the provided pid. +// +// This option conflicts with [WithTarget]. If both are used, [WithPID] +// will take precedence and be used. If OTEL_GO_AUTO_TARGET_EXE is defined, +// the pid passed here will take precedence. +// +// If multiple of these options are provided to an [Instrumentation], the last +// one will be used. +func WithPID(pid int) InstrumentationOption { + return fnOpt(func(c instConfig) instConfig { + c.target.Pid = pid + return c + }) +} diff --git a/internal/pkg/process/args.go b/internal/pkg/process/args.go index ad0c60e9a..0769b0dda 100644 --- a/internal/pkg/process/args.go +++ b/internal/pkg/process/args.go @@ -16,7 +16,9 @@ package process import ( "errors" + "fmt" "os" + "syscall" ) // ExePathEnvVar is the environment variable key whose value points to the @@ -26,10 +28,14 @@ const ExePathEnvVar = "OTEL_GO_AUTO_TARGET_EXE" // TargetArgs are the binary target information. type TargetArgs struct { ExePath string + Pid int } // Validate validates t and returns an error if not valid. func (t *TargetArgs) Validate() error { + if t.Pid != 0 { + return validatePID(t.Pid) + } if t.ExePath == "" { return errors.New("target binary path not specified, please specify " + ExePathEnvVar + " env variable") } @@ -37,15 +43,14 @@ func (t *TargetArgs) Validate() error { return nil } -// ParseTargetArgs returns TargetArgs for the target pointed to by the -// environment variable OTEL_GO_AUTO_TARGET_EXE. -func ParseTargetArgs() *TargetArgs { - result := &TargetArgs{} - - val, exists := os.LookupEnv(ExePathEnvVar) - if exists { - result.ExePath = val +func validatePID(pid int) error { + p, err := os.FindProcess(pid) + if err != nil { + return fmt.Errorf("can't find process with pid %d", pid) } - - return result + err = p.Signal(syscall.Signal(0)) + if err != nil { + return fmt.Errorf("process with pid %d does not exist", pid) + } + return nil } diff --git a/internal/pkg/process/discover.go b/internal/pkg/process/discover.go index dbc9b766a..9a63832bc 100644 --- a/internal/pkg/process/discover.go +++ b/internal/pkg/process/discover.go @@ -43,6 +43,9 @@ func NewAnalyzer() *Analyzer { // DiscoverProcessID searches for the target as an actively running process, // returning its PID if found. func (a *Analyzer) DiscoverProcessID(target *TargetArgs) (int, error) { + if target.Pid != 0 { + return target.Pid, nil + } for { select { case <-a.done: