Skip to content
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

Move main.go config into controller package #198

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
79 changes: 24 additions & 55 deletions cli_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"time"

"github.com/peterbourgon/ff/v3"
log "github.com/sirupsen/logrus"

"go.opentelemetry.io/ebpf-profiler/internal/controller"
"go.opentelemetry.io/ebpf-profiler/tracer"
)

Expand All @@ -27,8 +27,6 @@ const (

// This is the X in 2^(n + x) where n is the default hardcoded map size value
defaultArgMapScaleFactor = 0
// 1TB of executable address space
maxArgMapScaleFactor = 8
)

// Help strings for command line arguments
Expand All @@ -42,7 +40,7 @@ var (
mapScaleFactorHelp = fmt.Sprintf("Scaling factor for eBPF map sizes. "+
"Every increase by 1 doubles the map size. Increase if you see eBPF map size errors. "+
"Default is %d corresponding to 4GB of executable address space, max is %d.",
defaultArgMapScaleFactor, maxArgMapScaleFactor)
defaultArgMapScaleFactor, controller.MaxArgMapScaleFactor)
disableTLSHelp = "Disable encryption for data in transit."
bpfVerifierLogLevelHelp = "Log level of the eBPF verifier output (0,1,2). Default is 0."
versionHelp = "Show version."
Expand All @@ -65,84 +63,62 @@ var (
sendErrorFramesHelp = "Send error frames (devfiler only, breaks Kibana)"
)

type arguments struct {
bpfVerifierLogLevel uint
collAgentAddr string
copyright bool
disableTLS bool
mapScaleFactor uint
monitorInterval time.Duration
clockSyncInterval time.Duration
noKernelVersionCheck bool
pprofAddr string
probabilisticInterval time.Duration
probabilisticThreshold uint
reporterInterval time.Duration
samplesPerSecond int
sendErrorFrames bool
tracers string
verboseMode bool
version bool

fs *flag.FlagSet
}

// Package-scope variable, so that conditionally compiled other components can refer
// to the same flagset.

func parseArgs() (*arguments, error) {
var args arguments
func parseArgs() (*controller.Config, error) {
var args controller.Config

fs := flag.NewFlagSet("ebpf-profiler", flag.ExitOnError)

// Please keep the parameters ordered alphabetically in the source-code.
fs.UintVar(&args.bpfVerifierLogLevel, "bpf-log-level", 0, bpfVerifierLogLevelHelp)
fs.UintVar(&args.BpfVerifierLogLevel, "bpf-log-level", 0, bpfVerifierLogLevelHelp)

fs.StringVar(&args.collAgentAddr, "collection-agent", "", collAgentAddrHelp)
fs.BoolVar(&args.copyright, "copyright", false, copyrightHelp)
fs.StringVar(&args.CollAgentAddr, "collection-agent", "", collAgentAddrHelp)
fs.BoolVar(&args.Copyright, "copyright", false, copyrightHelp)

fs.BoolVar(&args.disableTLS, "disable-tls", false, disableTLSHelp)
fs.BoolVar(&args.DisableTLS, "disable-tls", false, disableTLSHelp)

fs.UintVar(&args.mapScaleFactor, "map-scale-factor",
fs.UintVar(&args.MapScaleFactor, "map-scale-factor",
defaultArgMapScaleFactor, mapScaleFactorHelp)

fs.DurationVar(&args.monitorInterval, "monitor-interval", defaultArgMonitorInterval,
fs.DurationVar(&args.MonitorInterval, "monitor-interval", defaultArgMonitorInterval,
monitorIntervalHelp)

fs.DurationVar(&args.clockSyncInterval, "clock-sync-interval", defaultClockSyncInterval,
fs.DurationVar(&args.ClockSyncInterval, "clock-sync-interval", defaultClockSyncInterval,
clockSyncIntervalHelp)

fs.BoolVar(&args.noKernelVersionCheck, "no-kernel-version-check", false,
fs.BoolVar(&args.NoKernelVersionCheck, "no-kernel-version-check", false,
noKernelVersionCheckHelp)

fs.StringVar(&args.pprofAddr, "pprof", "", pprofHelp)
fs.StringVar(&args.PprofAddr, "pprof", "", pprofHelp)

fs.DurationVar(&args.probabilisticInterval, "probabilistic-interval",
fs.DurationVar(&args.ProbabilisticInterval, "probabilistic-interval",
defaultProbabilisticInterval, probabilisticIntervalHelp)
fs.UintVar(&args.probabilisticThreshold, "probabilistic-threshold",
fs.UintVar(&args.ProbabilisticThreshold, "probabilistic-threshold",
defaultProbabilisticThreshold, probabilisticThresholdHelp)

fs.DurationVar(&args.reporterInterval, "reporter-interval", defaultArgReporterInterval,
fs.DurationVar(&args.ReporterInterval, "reporter-interval", defaultArgReporterInterval,
reporterIntervalHelp)

fs.IntVar(&args.samplesPerSecond, "samples-per-second", defaultArgSamplesPerSecond,
fs.IntVar(&args.SamplesPerSecond, "samples-per-second", defaultArgSamplesPerSecond,
samplesPerSecondHelp)

fs.BoolVar(&args.sendErrorFrames, "send-error-frames", defaultArgSendErrorFrames,
fs.BoolVar(&args.SendErrorFrames, "send-error-frames", defaultArgSendErrorFrames,
sendErrorFramesHelp)

fs.StringVar(&args.tracers, "t", "all", "Shorthand for -tracers.")
fs.StringVar(&args.tracers, "tracers", "all", tracersHelp)
fs.StringVar(&args.Tracers, "t", "all", "Shorthand for -tracers.")
fs.StringVar(&args.Tracers, "tracers", "all", tracersHelp)

fs.BoolVar(&args.verboseMode, "v", false, "Shorthand for -verbose.")
fs.BoolVar(&args.verboseMode, "verbose", false, verboseModeHelp)
fs.BoolVar(&args.version, "version", false, versionHelp)
fs.BoolVar(&args.VerboseMode, "v", false, "Shorthand for -verbose.")
fs.BoolVar(&args.VerboseMode, "verbose", false, verboseModeHelp)
fs.BoolVar(&args.Version, "version", false, versionHelp)

fs.Usage = func() {
fs.PrintDefaults()
}

args.fs = fs
args.Fs = fs

return &args, ff.Parse(fs, os.Args[1:],
ff.WithEnvVarPrefix("OTEL_PROFILING_AGENT"),
Expand All @@ -154,10 +130,3 @@ func parseArgs() (*arguments, error) {
ff.WithAllowMissingConfigFile(true),
)
}

func (args *arguments) dump() {
log.Debug("Config:")
args.fs.VisitAll(func(f *flag.Flag) {
log.Debug(fmt.Sprintf("%s: %v", f.Name, f.Value))
})
}
123 changes: 123 additions & 0 deletions internal/controller/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package controller // import "go.opentelemetry.io/ebpf-profiler/internal/controller"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about naming this internal package args instead of controller?
From a controller I expect something different, than providing a general configuration and validate it.
As this package is likely reused in the OTel profiling controller part, it should not get mixed and suggest something it doesn't do.

Suggested change
package controller // import "go.opentelemetry.io/ebpf-profiler/internal/controller"
package args // import "go.opentelemetry.io/ebpf-profiler/internal/args"

Copy link
Member Author

@dmathieu dmathieu Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to be the first of a few PRs, with the intent to extract the setup of main.go into a shared package.
I named this package controller, because what it will do in the end is run the agent, either standalone, or as a collector receiver.

I'm open to a name different than controller, but args would be specific to this config, which doesn't do enough to be in its own package IMHO.


import (
"errors"
"flag"
"fmt"
"runtime"
"time"

log "github.com/sirupsen/logrus"
"go.opentelemetry.io/ebpf-profiler/tracer"
)

type Config struct {
BpfVerifierLogLevel uint
CollAgentAddr string
Copyright bool
DisableTLS bool
MapScaleFactor uint
MonitorInterval time.Duration
ClockSyncInterval time.Duration
NoKernelVersionCheck bool
PprofAddr string
ProbabilisticInterval time.Duration
ProbabilisticThreshold uint
ReporterInterval time.Duration
SamplesPerSecond int
SendErrorFrames bool
Tracers string
VerboseMode bool
Version bool

Fs *flag.FlagSet
}

const (
exitParseError int = 2

// 1TB of executable address space
MaxArgMapScaleFactor = 8
)

// Dump visits all flag sets, and dumps them all to debug
// Used for verbose mode logging.
func (cfg *Config) Dump() {
log.Debug("Config:")
cfg.Fs.VisitAll(func(f *flag.Flag) {
log.Debug(fmt.Sprintf("%s: %v", f.Name, f.Value))
})
}

// Validate runs validations on the provided configuration, and returns errors
// if invalid values were provided.
func (cfg *Config) Validate() error {
if cfg.SamplesPerSecond < 1 {
return ErrorWithExitCode{
error: fmt.Errorf("invalid sampling frequency: %d", cfg.SamplesPerSecond),
code: exitParseError,
}
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
}

if cfg.MapScaleFactor > 8 {
return ErrorWithExitCode{
error: fmt.Errorf("eBPF map scaling factor %d exceeds limit (max: %d)",
cfg.MapScaleFactor, MaxArgMapScaleFactor),
code: exitParseError,
}
}

if cfg.BpfVerifierLogLevel > 2 {
return ErrorWithExitCode{
error: fmt.Errorf("invalid eBPF verifier log level: %d", cfg.BpfVerifierLogLevel),
code: exitParseError,
}
}

if cfg.ProbabilisticInterval < 1*time.Minute || cfg.ProbabilisticInterval > 5*time.Minute {
return ErrorWithExitCode{
error: errors.New("invalid argument for probabilistic-interval: use " +
"a duration between 1 and 5 minutes"),
code: exitParseError,
}
}

if cfg.ProbabilisticThreshold < 1 ||
cfg.ProbabilisticThreshold > tracer.ProbabilisticThresholdMax {
return ErrorWithExitCode{
error: fmt.Errorf("invalid argument for probabilistic-threshold. Value "+
"should be between 1 and %d", tracer.ProbabilisticThresholdMax),
code: exitParseError,
}
}

if !cfg.NoKernelVersionCheck {
major, minor, patch, err := tracer.GetCurrentKernelVersion()
if err != nil {
return fmt.Errorf("failed to get kernel version: %v", err)
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
}

var minMajor, minMinor uint32
switch runtime.GOARCH {
case "amd64":
if cfg.VerboseMode {
minMajor, minMinor = 5, 2
} else {
minMajor, minMinor = 4, 19
}
case "arm64":
// Older ARM64 kernel versions have broken bpf_probe_read.
// https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47
minMajor, minMinor = 5, 5
default:
return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
}

if major < minMajor || (major == minMajor && minor < minMinor) {
return fmt.Errorf("host Agent requires kernel version "+
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
"%d.%d or newer but got %d.%d.%d", minMajor, minMinor, major, minor, patch)
}
}

return nil
}
13 changes: 13 additions & 0 deletions internal/controller/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package controller // import "go.opentelemetry.io/ebpf-profiler/internal/controller"
dmathieu marked this conversation as resolved.
Show resolved Hide resolved

// ErrorWithExitCode provides an error with an exit code
// Used to be able to return errors with the exit code the CLI is expected to
// return when exiting.
type ErrorWithExitCode struct {
error
code int
}

func (e ErrorWithExitCode) Code() int {
return e.code
}
Loading