summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-03-29 18:19:06 +0200
committerPaul Buetow <paul@buetow.org>2025-03-29 18:19:06 +0200
commitb8ac7c88b203ba5475cbca63669ce654c2ed49ea (patch)
tree3fc6d2539ced1d4012f9bf0fe69d4d191e2a5049
parentdc5c405f6afb9fc59ba73e7dbc7cd3564983ae6e (diff)
flags is now a singleton
-rw-r--r--cmd/ior/main.go3
-rw-r--r--internal/eventfilter.go14
-rw-r--r--internal/eventloop.go20
-rw-r--r--internal/flags/flags.go41
-rw-r--r--internal/flamegraph/flamegraph.go4
-rw-r--r--internal/flamegraph/iordata.go15
-rw-r--r--internal/ior.go20
7 files changed, 60 insertions, 57 deletions
diff --git a/cmd/ior/main.go b/cmd/ior/main.go
index 2d8584a..f085c60 100644
--- a/cmd/ior/main.go
+++ b/cmd/ior/main.go
@@ -10,5 +10,6 @@ func main() {
if runtime.GOOS != "linux" {
panic("Unsupported OS")
}
- internal.Run(flags.New())
+ flags.Parse()
+ internal.Run()
}
diff --git a/internal/eventfilter.go b/internal/eventfilter.go
index 71995b1..0b3f121 100644
--- a/internal/eventfilter.go
+++ b/internal/eventfilter.go
@@ -15,23 +15,23 @@ type eventFilter struct {
pathFilter string
}
-func newEventFilter(flags flags.Flags) *eventFilter {
+func newEventFilter() *eventFilter {
var ef eventFilter
- if flags.CommFilter != "" {
- if len(flags.CommFilter) > types.MAX_FILENAME_LENGTH {
+ if flags.Get().CommFilter != "" {
+ if len(flags.Get().CommFilter) > types.MAX_FILENAME_LENGTH {
panic(fmt.Sprintf("Comm filter's max size is %d", types.MAX_PROGNAME_LENGTH))
}
ef.commFilterEnable = true
- ef.commFilter = flags.CommFilter
+ ef.commFilter = flags.Get().CommFilter
}
- if flags.PathFilter != "" {
- if len(flags.PathFilter) > types.MAX_FILENAME_LENGTH {
+ if flags.Get().PathFilter != "" {
+ if len(flags.Get().PathFilter) > types.MAX_FILENAME_LENGTH {
panic(fmt.Sprintf("Path filter's max size is %d", types.MAX_FILENAME_LENGTH))
}
ef.pathFilterEnable = true
- ef.pathFilter = flags.PathFilter
+ ef.pathFilter = flags.Get().PathFilter
}
return &ef
diff --git a/internal/eventloop.go b/internal/eventloop.go
index 4e9a3cb..f52274d 100644
--- a/internal/eventloop.go
+++ b/internal/eventloop.go
@@ -20,7 +20,6 @@ import (
// TOOD: read and write syscalls: can also collect amount of bytes!
type eventLoop struct {
- flags flags.Flags
filter *eventFilter
enterEvs map[uint32]*event.Pair // Temp. store of sys_enter tracepoints per Tid.
files map[int32]file.File // Track all open files by file descriptor..
@@ -37,15 +36,14 @@ type eventLoop struct {
done chan struct{}
}
-func newEventLoop(flags flags.Flags) *eventLoop {
+func newEventLoop() *eventLoop {
return &eventLoop{
- flags: flags,
- filter: newEventFilter(flags),
+ filter: newEventFilter(),
enterEvs: make(map[uint32]*event.Pair),
files: make(map[int32]file.File),
comms: make(map[uint32]string),
prevPairTimes: make(map[uint32]uint64),
- flamegraph: flamegraph.New(flags),
+ flamegraph: flamegraph.New(),
done: make(chan struct{}),
}
}
@@ -73,23 +71,23 @@ func (e *eventLoop) stats() string {
func (e *eventLoop) run(ctx context.Context, rawCh <-chan []byte) {
defer close(e.done)
- if e.flags.FlamegraphEnable {
+ if flags.Get().FlamegraphEnable {
fmt.Println("Collecting flame graph stats, press Ctrl+C to stop")
e.flamegraph.Start(ctx)
}
- if e.flags.PprofEnable {
+ if flags.Get().PprofEnable {
fmt.Println("Profiling, press Ctrl+C to stop")
}
- if !e.flags.FlamegraphEnable && !e.flags.PprofEnable {
+ if !flags.Get().FlamegraphEnable && !flags.Get().PprofEnable {
fmt.Println(event.EventStreamHeader)
}
e.startTime = time.Now()
for ev := range e.events(ctx, rawCh) {
switch {
- case e.flags.FlamegraphEnable:
+ case flags.Get().FlamegraphEnable:
e.flamegraph.Ch <- ev
- case e.flags.PprofEnable:
+ case flags.Get().PprofEnable:
ev.Recycle()
default:
fmt.Println(ev.String())
@@ -98,7 +96,7 @@ func (e *eventLoop) run(ctx context.Context, rawCh <-chan []byte) {
e.numSyscallsAfterFilter++
}
- if e.flags.FlamegraphEnable {
+ if flags.Get().FlamegraphEnable {
fmt.Println("Waiting for flamegraph")
<-e.flamegraph.Done
}
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index a85c838..c971335 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -6,10 +6,18 @@ import (
"os"
"regexp"
"strings"
+ "sync"
bpf "github.com/aquasecurity/libbpfgo"
)
+var singleton Flags
+var once sync.Once
+
+func Get() Flags {
+ return singleton
+}
+
type Flags struct {
PidFilter int
TidFilter int
@@ -28,29 +36,32 @@ type Flags struct {
FlamegraphName string // If set, enables new style iorData output, TODO: remove comment once old style collapsed format is retired
}
-func New() (flags Flags) {
- flag.IntVar(&flags.PidFilter, "pid", -1, "Filter for processes ID")
- flag.IntVar(&flags.TidFilter, "tid", -1, "Filter for thread ID")
- flag.IntVar(&flags.EventMapSize, "mapSize", 4096*16, "BPF FD event ring buffer map size")
- flag.IntVar(&flags.Duration, "duration", 60, "Probe duration in seconds")
+func Parse() {
+ once.Do(func() {
+ parse()
+ })
+}
- flag.StringVar(&flags.CommFilter, "comm", "", "Command to filter for")
- flag.StringVar(&flags.PathFilter, "path", "", "Path to filter for")
+func parse() {
+ flag.IntVar(&singleton.PidFilter, "pid", -1, "Filter for processes ID")
+ flag.IntVar(&singleton.TidFilter, "tid", -1, "Filter for thread ID")
+ flag.IntVar(&singleton.EventMapSize, "mapSize", 4096*16, "BPF FD event ring buffer map size")
+ flag.IntVar(&singleton.Duration, "duration", 60, "Probe duration in seconds")
- flag.BoolVar(&flags.PprofEnable, "pprof", false, "Enable profiling")
+ flag.StringVar(&singleton.CommFilter, "comm", "", "Command to filter for")
+ flag.StringVar(&singleton.PathFilter, "path", "", "Path to filter for")
+
+ flag.BoolVar(&singleton.PprofEnable, "pprof", false, "Enable profiling")
tracepointsToAttach := flag.String("tps", "", "Comma separated list regexes for tracepoints to load")
tracepointsToExclude := flag.String("tpsExclude", "", "Comma separated list regexes for tracepoints to exclude")
- flag.BoolVar(&flags.FlamegraphEnable, "flamegraph", false, "Enable flamegraph builder")
- flag.StringVar(&flags.FlamegraphName, "name", "", "Name of the flamegraph data output")
-
+ flag.BoolVar(&singleton.FlamegraphEnable, "flamegraph", false, "Enable flamegraph builder")
+ flag.StringVar(&singleton.FlamegraphName, "name", "", "Name of the flamegraph data output")
flag.Parse()
- flags.TracepointsToAttach = extractTracepointFlags(*tracepointsToAttach)
- flags.TracepointsToExclude = extractTracepointFlags(*tracepointsToExclude)
-
- return flags
+ singleton.TracepointsToAttach = extractTracepointFlags(*tracepointsToAttach)
+ singleton.TracepointsToExclude = extractTracepointFlags(*tracepointsToExclude)
}
func extractTracepointFlags(tracepoints string) (regexes []*regexp.Regexp) {
diff --git a/internal/flamegraph/flamegraph.go b/internal/flamegraph/flamegraph.go
index 9e1e14b..aeb5143 100644
--- a/internal/flamegraph/flamegraph.go
+++ b/internal/flamegraph/flamegraph.go
@@ -18,7 +18,7 @@ type Flamegraph struct {
workers []worker
}
-func New(flags flags.Flags) Flamegraph {
+func New() Flamegraph {
f := Flamegraph{
Ch: make(chan *event.Pair, 4096),
Done: make(chan struct{}),
@@ -41,7 +41,7 @@ func (f Flamegraph) Start(ctx context.Context) {
for i, worker := range f.workers {
fmt.Println("Starting flamegraph worker", i)
- if f.flags.FlamegraphName == "" { // Empty string means: old style collapsed
+ if flags.Get().FlamegraphName == "" { // Empty string means: old style collapsed
go worker.runCollapsed(ctx, &wg, f.Ch)
} else {
go worker.run(ctx, &wg, f.Ch)
diff --git a/internal/flamegraph/iordata.go b/internal/flamegraph/iordata.go
index c167f22..b6bb197 100644
--- a/internal/flamegraph/iordata.go
+++ b/internal/flamegraph/iordata.go
@@ -21,7 +21,6 @@ type flagsType = string
type pathMap map[pathType]map[traceIdType]map[commType]map[pidType]map[tidType]map[flagsType]counter
type iorData struct {
- flags flags.Flags
paths pathMap
}
@@ -29,11 +28,8 @@ type iorData struct {
// TODO: Name flag for iorData (outfile format: hostname-name-timestamp.ior.zst)
// TODO: Output path for iorData flag
// TODO: Add helper to convert .ior data file to collapsed format
-func newIorData(flags flags.Flags) iorData {
- return iorData{
- flags: flags,
- paths: make(pathMap),
- }
+func newIorData() iorData {
+ return iorData{paths: make(pathMap)}
}
// TODO: Unit test
@@ -100,7 +96,7 @@ func (iod iorData) commit() error {
panic(err)
}
- filename := fmt.Sprintf("%s-%s-%s.ior.zst", hostname, iod.flags.FlamegraphName,
+ filename := fmt.Sprintf("%s-%s-%s.ior.zst", hostname, flags.Get().FlamegraphName,
time.Now().Format("2006-01-02_15:04:05"))
file, err := os.Create(filename)
if err != nil {
@@ -108,10 +104,7 @@ func (iod iorData) commit() error {
}
defer file.Close()
- encoder, err := zstd.NewWriter(file)
- if err != nil {
- return err
- }
+ encoder := zstd.NewWriter(file)
defer encoder.Close()
jsonEncoder := json.NewEncoder(encoder)
diff --git a/internal/ior.go b/internal/ior.go
index 7bf96bc..3d6fd1e 100644
--- a/internal/ior.go
+++ b/internal/ior.go
@@ -19,9 +19,9 @@ import (
// TODO: Generally, write unit tests
// TODO: Integration tests, write C or Cgo code to simulate I/O?
-func attachTracepoints(flags flags.Flags, bpfModule *bpf.Module) error {
+func attachTracepoints(bpfModule *bpf.Module) error {
for _, name := range tracepoints.List {
- if !flags.ShouldIAttachTracepoint(name) {
+ if !flags.Get().ShouldIAttachTracepoint(name) {
continue
}
fmt.Println("Attaching tracepoint", name)
@@ -43,18 +43,18 @@ func attachTracepoints(flags flags.Flags, bpfModule *bpf.Module) error {
return nil
}
-func Run(flags flags.Flags) {
+func Run() {
bpfModule, err := bpf.NewModuleFromFile("ior.bpf.o")
if err != nil {
panic(err)
}
defer bpfModule.Close()
- if err := flags.ResizeBPFMaps(bpfModule); err != nil {
+ if err := flags.Get().ResizeBPFMaps(bpfModule); err != nil {
panic(err)
}
- if err := flags.SetBPF(bpfModule); err != nil {
+ if err := flags.Get().SetBPF(bpfModule); err != nil {
panic(err)
}
@@ -62,7 +62,7 @@ func Run(flags flags.Flags) {
panic(err)
}
- if err := attachTracepoints(flags, bpfModule); err != nil {
+ if err := attachTracepoints(bpfModule); err != nil {
panic(err)
}
@@ -76,7 +76,7 @@ func Run(flags flags.Flags) {
pprofDone := make(chan struct{})
var cpuProfile, memProfile *os.File
- if flags.PprofEnable {
+ if flags.Get().PprofEnable {
if cpuProfile, err = os.Create("ior.cpuprofile"); err != nil {
panic(err)
}
@@ -88,8 +88,8 @@ func Run(flags flags.Flags) {
close(pprofDone)
}
- loop := newEventLoop(flags)
- duration := time.Duration(flags.Duration) * time.Second
+ loop := newEventLoop()
+ duration := time.Duration(flags.Get().Duration) * time.Second
fmt.Println("Probing for", duration)
ctx, cancel := context.WithTimeout(context.Background(), duration)
@@ -105,7 +105,7 @@ func Run(flags flags.Flags) {
go func() {
<-ctx.Done()
fmt.Println(loop.stats())
- if flags.PprofEnable {
+ if flags.Get().PprofEnable {
fmt.Println("Stoppig profiling, writing ior.cpuprofile and ior.memprofile")
pprof.StopCPUProfile()
pprof.WriteHeapProfile(memProfile)