Skip to content

Commit

Permalink
Add WithPID InstrumentationOption (#355)
Browse files Browse the repository at this point in the history
* Add WithPID InstrumentationOption

* Update changelog

* Improve validation for inst config

* WIP

* more WIP

* Remove exePath checks

* Fix lint

* remove traces-orig.json

* .

* Init log if not initialized in NewInstrumentation

* fix os.Exit

---------

Co-authored-by: Eden Federman <eden@keyval.dev>
  • Loading branch information
RonFed and edeNFed authored Oct 9, 2023
1 parent ffb86fe commit d2dedd1
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=$*
Expand Down
15 changes: 15 additions & 0 deletions instConfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
43 changes: 36 additions & 7 deletions instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -122,17 +130,19 @@ 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
}

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
Expand All @@ -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
Expand Down Expand Up @@ -190,14 +200,17 @@ 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.
//
// If OTEL_GO_AUTO_TARGET_EXE is defined it will take precedence over any value
// 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
})
}
Expand All @@ -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
})
}
25 changes: 15 additions & 10 deletions internal/pkg/process/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package process

import (
"errors"
"fmt"
"os"
"syscall"
)

// ExePathEnvVar is the environment variable key whose value points to the
Expand All @@ -26,26 +28,29 @@ 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")
}

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
}
3 changes: 3 additions & 0 deletions internal/pkg/process/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit d2dedd1

Please sign in to comment.