From dc3a84baa523e45153871aa2ab10d080840d4fc0 Mon Sep 17 00:00:00 2001 From: Nicolas Lacasse Date: Wed, 24 Aug 2022 12:24:39 -0700 Subject: [PATCH] Create gofer profiles when profile flags are passed to runsc. Previously, when passing --profile and related profile flags to runsc, we only created profile files for the sentry. The gofer was ignored. This CL plumbs profile flags to the gofer, and creates profile files for the gofer as well. For now, the gofer profiles are put in separate files from the sentry profile. The gofer profile files are suffixed with "gofer" and the container id, to avoid collision with the sentry profiles and profiles from other gofers (since each container has its own gofer). In a future CL, the gofer profile data will be merged with the sentry profile data into a single file. PiperOrigin-RevId: 469794523 --- runsc/boot/BUILD | 2 +- runsc/boot/loader.go | 21 +-- runsc/boot/profile.go | 95 ------------- runsc/cmd/BUILD | 1 + runsc/cmd/boot.go | 38 ++---- runsc/cmd/gofer.go | 26 +++- runsc/container/container.go | 44 ++++++ runsc/fsgofer/filter/BUILD | 1 + runsc/fsgofer/filter/config_profile.go | 47 +++++++ runsc/fsgofer/filter/filter.go | 33 +++-- runsc/profile/BUILD | 16 +++ runsc/profile/profile.go | 179 +++++++++++++++++++++++++ 12 files changed, 349 insertions(+), 154 deletions(-) delete mode 100644 runsc/boot/profile.go create mode 100644 runsc/fsgofer/filter/config_profile.go create mode 100644 runsc/profile/BUILD create mode 100644 runsc/profile/profile.go diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 389bcb301..998ad5cf7 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -15,7 +15,6 @@ go_library( "loader.go", "mount_hints.go", "network.go", - "profile.go", "seccheck.go", "strace.go", "vfs.go", @@ -116,6 +115,7 @@ go_library( "//runsc/boot/pprof", "//runsc/boot/procfs", "//runsc/config", + "//runsc/profile", "//runsc/specutils", "//runsc/specutils/seccomp", "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 166b19a9d..f78df13da 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -74,6 +74,7 @@ import ( _ "gvisor.dev/gvisor/runsc/boot/platforms" // register all platforms. "gvisor.dev/gvisor/runsc/boot/pprof" "gvisor.dev/gvisor/runsc/config" + "gvisor.dev/gvisor/runsc/profile" "gvisor.dev/gvisor/runsc/specutils" "gvisor.dev/gvisor/runsc/specutils/seccomp" @@ -205,21 +206,6 @@ type Args struct { TotalMem uint64 // UserLogFD is the file descriptor to write user logs to. UserLogFD int - // ProfileBlockFD is the file descriptor to write a block profile to. - // Valid if >=0. - ProfileBlockFD int - // ProfileCPUFD is the file descriptor to write a CPU profile to. - // Valid if >=0. - ProfileCPUFD int - // ProfileHeapFD is the file descriptor to write a heap profile to. - // Valid if >=0. - ProfileHeapFD int - // ProfileMutexFD is the file descriptor to write a mutex profile to. - // Valid if >=0. - ProfileMutexFD int - // TraceFD is the file descriptor to write a Go execution trace to. - // Valid if >=0. - TraceFD int // ProductName is the value to show in // /sys/devices/virtual/dmi/id/product_name. ProductName string @@ -229,6 +215,9 @@ type Args struct { // SinkFDs is an ordered array of file descriptors to be used by seccheck // sinks configured from the --pod-init-config file. SinkFDs []int + // ProfileOpts contains the set of profiles to enable and the + // corresponding FDs where profile data will be written. + ProfileOpts profile.Opts } // make sure stdioFDs are always the same on initial start and on restore @@ -237,7 +226,7 @@ const startingStdioFD = 256 // New initializes a new kernel loader configured by spec. // New also handles setting up a kernel for restoring a container. func New(args Args) (*Loader, error) { - stopProfiling := startProfiling(args) + stopProfiling := profile.Start(args.ProfileOpts) // We initialize the rand package now to make sure /dev/urandom is pre-opened // on kernels that do not support getrandom(2). diff --git a/runsc/boot/profile.go b/runsc/boot/profile.go deleted file mode 100644 index 3ecd3e532..000000000 --- a/runsc/boot/profile.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2021 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package boot - -import ( - "os" - "runtime" - "runtime/pprof" - "runtime/trace" - - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/control" -) - -// startProfiling initiates profiling as defined by the ProfileConfig, and -// returns a function that should be called to stop profiling. -func startProfiling(args Args) func() { - var onStopProfiling []func() - stopProfiling := func() { - for _, f := range onStopProfiling { - f() - } - } - - if args.ProfileBlockFD >= 0 { - file := os.NewFile(uintptr(args.ProfileBlockFD), "profile-block") - - runtime.SetBlockProfileRate(control.DefaultBlockProfileRate) - onStopProfiling = append(onStopProfiling, func() { - if err := pprof.Lookup("block").WriteTo(file, 0); err != nil { - log.Warningf("Error writing block profile: %v", err) - } - file.Close() - runtime.SetBlockProfileRate(0) - }) - } - - if args.ProfileCPUFD >= 0 { - file := os.NewFile(uintptr(args.ProfileCPUFD), "profile-cpu") - - pprof.StartCPUProfile(file) - onStopProfiling = append(onStopProfiling, func() { - pprof.StopCPUProfile() - file.Close() - }) - } - - if args.ProfileHeapFD >= 0 { - file := os.NewFile(uintptr(args.ProfileHeapFD), "profile-heap") - - onStopProfiling = append(onStopProfiling, func() { - if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil { - log.Warningf("Error writing heap profile: %v", err) - } - file.Close() - }) - } - - if args.ProfileMutexFD >= 0 { - file := os.NewFile(uintptr(args.ProfileMutexFD), "profile-mutex") - - prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate) - onStopProfiling = append(onStopProfiling, func() { - if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil { - log.Warningf("Error writing mutex profile: %v", err) - } - file.Close() - runtime.SetMutexProfileFraction(prev) - }) - } - - if args.TraceFD >= 0 { - file := os.NewFile(uintptr(args.TraceFD), "trace") - - trace.Start(file) - onStopProfiling = append(onStopProfiling, func() { - trace.Stop() - file.Close() - }) - } - - return stopProfiling -} diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index fc6e68e1d..8a1ac21af 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -65,6 +65,7 @@ go_library( "//runsc/fsgofer", "//runsc/fsgofer/filter", "//runsc/mitigate", + "//runsc/profile", "//runsc/specutils", "@com_github_google_subcommands//:go_default_library", "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go index b35051a15..95143e90b 100644 --- a/runsc/cmd/boot.go +++ b/runsc/cmd/boot.go @@ -31,6 +31,7 @@ import ( "gvisor.dev/gvisor/runsc/cmd/util" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/flag" + "gvisor.dev/gvisor/runsc/profile" "gvisor.dev/gvisor/runsc/specutils" ) @@ -82,26 +83,6 @@ type Boot struct { // sandbox (e.g. gofer) and sent through this FD. mountsFD int - // profileBlockFD is the file descriptor to write a block profile to. - // Valid if >= 0. - profileBlockFD int - - // profileCPUFD is the file descriptor to write a CPU profile to. - // Valid if >= 0. - profileCPUFD int - - // profileHeapFD is the file descriptor to write a heap profile to. - // Valid if >= 0. - profileHeapFD int - - // profileMutexFD is the file descriptor to write a mutex profile to. - // Valid if >= 0. - profileMutexFD int - - // traceFD is the file descriptor to write a Go execution trace to. - // Valid if >= 0. - traceFD int - podInitConfigFD int sinkFDs intFlags @@ -117,6 +98,9 @@ type Boot struct { // productName is the value to show in // /sys/devices/virtual/dmi/id/product_name. productName string + + // FDs for profile data. + profileFDs profile.FDArgs } // Name implements subcommands.Command.Name. @@ -154,13 +138,11 @@ func (b *Boot) SetFlags(f *flag.FlagSet) { f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.") f.IntVar(&b.startSyncFD, "start-sync-fd", -1, "required FD to used to synchronize sandbox startup") f.IntVar(&b.mountsFD, "mounts-fd", -1, "mountsFD is the file descriptor to read list of mounts after they have been resolved (direct paths, no symlinks).") - f.IntVar(&b.profileBlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.") - f.IntVar(&b.profileCPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.") - f.IntVar(&b.profileHeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.") - f.IntVar(&b.profileMutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.") - f.IntVar(&b.traceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.") f.IntVar(&b.podInitConfigFD, "pod-init-config-fd", -1, "file descriptor to the pod init configuration file.") f.Var(&b.sinkFDs, "sink-fds", "ordered list of file descriptors to be used by the sinks defined in --pod-init-config.") + + // Profiling flags. + b.profileFDs.SetFromFlags(f) } // Execute implements subcommands.Command.Execute. It starts a sandbox in a @@ -308,14 +290,10 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) NumCPU: b.cpuNum, TotalMem: b.totalMem, UserLogFD: b.userLogFD, - ProfileBlockFD: b.profileBlockFD, - ProfileCPUFD: b.profileCPUFD, - ProfileHeapFD: b.profileHeapFD, - ProfileMutexFD: b.profileMutexFD, - TraceFD: b.traceFD, ProductName: b.productName, PodInitConfigFD: b.podInitConfigFD, SinkFDs: b.sinkFDs.GetArray(), + ProfileOpts: b.profileFDs.ToOpts(), } l, err := boot.New(bootArgs) if err != nil { diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 88c1c6ade..a04d7a2ba 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -37,6 +37,7 @@ import ( "gvisor.dev/gvisor/runsc/flag" "gvisor.dev/gvisor/runsc/fsgofer" "gvisor.dev/gvisor/runsc/fsgofer/filter" + "gvisor.dev/gvisor/runsc/profile" "gvisor.dev/gvisor/runsc/specutils" ) @@ -68,6 +69,9 @@ type Gofer struct { specFD int mountsFD int syncUsernsFD int + + profileFDs profile.FDArgs + stopProfiling func() } // Name implements subcommands.Command. @@ -96,6 +100,9 @@ func (g *Gofer) SetFlags(f *flag.FlagSet) { f.IntVar(&g.specFD, "spec-fd", -1, "required fd with the container spec") f.IntVar(&g.mountsFD, "mounts-fd", -1, "mountsFD is the file descriptor to write list of mounts after they have been resolved (direct paths, no symlinks).") f.IntVar(&g.syncUsernsFD, "sync-userns-fd", -1, "file descriptor used to synchronize rootless user namespace initialization.") + + // Profiling flags. + g.profileFDs.SetFromFlags(f) } // Execute implements subcommands.Command. @@ -156,6 +163,11 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // We expcec that setCapsAndCallSelfsetCapsAndCallSelf has to be called in this case. panic("unreachable") } + + // Start profiling. This will be a noop if no profiling arguments were passed. + profileOpts := g.profileFDs.ToOpts() + g.stopProfiling = profile.Start(profileOpts) + // At this point we won't re-execute, so it's safe to limit via rlimits. Any // limit >= 0 works. If the limit is lower than the current number of open // files, then Setrlimit will succeed, and the next open will fail. @@ -214,11 +226,11 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) log.Infof("Process chroot'd to %q", root) // Initialize filters. - if conf.FSGoferHostUDS { - filter.InstallUDSFilters() + opts := filter.Options{ + UDSEnabled: conf.FSGoferHostUDS, + ProfileEnabled: len(profileOpts) > 0, } - - if err := filter.Install(); err != nil { + if err := filter.Install(opts); err != nil { util.Fatalf("installing seccomp filters: %v", err) } @@ -295,6 +307,9 @@ func (g *Gofer) serveLisafs(spec *specs.Spec, conf *config.Config, root string) server.Wait() server.Destroy() log.Infof("All lisafs servers exited.") + if g.stopProfiling != nil { + g.stopProfiling() + } return subcommands.ExitSuccess } @@ -353,6 +368,9 @@ func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subc } wg.Wait() log.Infof("All 9P servers exited.") + if g.stopProfiling != nil { + g.stopProfiling() + } return subcommands.ExitSuccess } diff --git a/runsc/container/container.go b/runsc/container/container.go index dcd28acc6..8e0ff3b85 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -927,6 +927,11 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu } donations.DonateAndClose("spec-fd", specFile) + // Donate any profile FDs to the gofer. + if err := c.donateGoferProfileFDs(conf, &donations); err != nil { + return nil, nil, fmt.Errorf("donating gofer profile fds: %w", err) + } + // Create pipe that allows gofer to send mount list to sandbox after all paths // have been resolved. mountsSand, mountsGofer, err := os.Pipe() @@ -1341,6 +1346,45 @@ func (c *Container) setupCgroupForSubcontainer(conf *config.Config, spec *specs. return cgroupInstall(conf, cg, &specs.LinuxResources{}) } +// donateGoferProfileFDs will open profile files and donate their FDs to the +// gofer. +func (c *Container) donateGoferProfileFDs(conf *config.Config, donations *donation.Agency) error { + // The gofer profile files are named based on the provided flag, but + // suffixed with "gofer" and the container ID to avoid collisions with + // sentry profile files or profile files from other gofers. + // + // TODO(b/243183772): Merge gofer profile data with sentry profile data + // into a single file. + profSuffix := ".gofer." + c.ID + const profFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC + if conf.ProfileBlock != "" { + if err := donations.OpenAndDonate("profile-block-fd", conf.ProfileBlock+profSuffix, profFlags); err != nil { + return err + } + } + if conf.ProfileCPU != "" { + if err := donations.OpenAndDonate("profile-cpu-fd", conf.ProfileCPU+profSuffix, profFlags); err != nil { + return err + } + } + if conf.ProfileHeap != "" { + if err := donations.OpenAndDonate("profile-heap-fd", conf.ProfileHeap+profSuffix, profFlags); err != nil { + return err + } + } + if conf.ProfileMutex != "" { + if err := donations.OpenAndDonate("profile-mutex-fd", conf.ProfileMutex+profSuffix, profFlags); err != nil { + return err + } + } + if conf.TraceFile != "" { + if err := donations.OpenAndDonate("trace-fd", conf.TraceFile+profSuffix, profFlags); err != nil { + return err + } + } + return nil +} + // cgroupInstall creates cgroups dir structure and sets their respective // resources. In case of success, returns the cgroups instance and nil error. // For rootless, it's possible that cgroups operations fail, in this case the diff --git a/runsc/fsgofer/filter/BUILD b/runsc/fsgofer/filter/BUILD index 9707edd68..886275991 100644 --- a/runsc/fsgofer/filter/BUILD +++ b/runsc/fsgofer/filter/BUILD @@ -8,6 +8,7 @@ go_library( "config.go", "config_amd64.go", "config_arm64.go", + "config_profile.go", "extra_filters.go", "extra_filters_msan.go", "extra_filters_race.go", diff --git a/runsc/fsgofer/filter/config_profile.go b/runsc/fsgofer/filter/config_profile.go new file mode 100644 index 000000000..358448243 --- /dev/null +++ b/runsc/fsgofer/filter/config_profile.go @@ -0,0 +1,47 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filter + +import ( + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/seccomp" +) + +var profileFilters = seccomp.SyscallRules{ + unix.SYS_OPENAT: []seccomp.Rule{ + { + seccomp.MatchAny{}, + seccomp.MatchAny{}, + seccomp.EqualTo(unix.O_RDONLY | unix.O_LARGEFILE | unix.O_CLOEXEC), + }, + }, + unix.SYS_SETITIMER: {}, + unix.SYS_TIMER_CREATE: []seccomp.Rule{ + { + seccomp.EqualTo(unix.CLOCK_THREAD_CPUTIME_ID), /* which */ + seccomp.MatchAny{}, /* sevp */ + seccomp.MatchAny{}, /* timerid */ + }, + }, + unix.SYS_TIMER_DELETE: []seccomp.Rule{}, + unix.SYS_TIMER_SETTIME: []seccomp.Rule{ + { + seccomp.MatchAny{}, /* timerid */ + seccomp.EqualTo(0), /* flags */ + seccomp.MatchAny{}, /* new_value */ + seccomp.EqualTo(0), /* old_value */ + }, + }, +} diff --git a/runsc/fsgofer/filter/filter.go b/runsc/fsgofer/filter/filter.go index e87fc81d9..8665c548b 100644 --- a/runsc/fsgofer/filter/filter.go +++ b/runsc/fsgofer/filter/filter.go @@ -18,21 +18,38 @@ package filter import ( + "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/seccomp" ) +// Options are seccomp filter related options. +type Options struct { + UDSEnabled bool + ProfileEnabled bool +} + // Install installs seccomp filters. -func Install() error { +func Install(opt Options) error { + s := allowedSyscalls + + if opt.ProfileEnabled { + report("profile enabled: syscall filters less restrictive!") + s.Merge(profileFilters) + } + + if opt.UDSEnabled { + report("host UDS enabled: syscall filters less restrictive!") + s.Merge(udsSyscalls) + } + // Set of additional filters used by -race and -msan. Returns empty // when not enabled. - allowedSyscalls.Merge(instrumentationFilters()) + s.Merge(instrumentationFilters()) - return seccomp.Install(allowedSyscalls, seccomp.DenyNewExecMappings) + return seccomp.Install(s, seccomp.DenyNewExecMappings) } -// InstallUDSFilters extends the allowed syscalls to include those necessary for -// creating and connecting to host UDS. -func InstallUDSFilters() { - // Add additional filters required for connecting to the host's sockets. - allowedSyscalls.Merge(udsSyscalls) +// report writes a warning message to the log. +func report(msg string) { + log.Warningf("*** SECCOMP WARNING: %s", msg) } diff --git a/runsc/profile/BUILD b/runsc/profile/BUILD new file mode 100644 index 000000000..aa3a4f4f2 --- /dev/null +++ b/runsc/profile/BUILD @@ -0,0 +1,16 @@ +load("//tools:defs.bzl", "go_library") + +package(licenses = ["notice"]) + +go_library( + name = "profile", + srcs = ["profile.go"], + visibility = [ + "//runsc:__subpackages__", + ], + deps = [ + "//pkg/log", + "//pkg/sentry/control", + "//runsc/flag", + ], +) diff --git a/runsc/profile/profile.go b/runsc/profile/profile.go new file mode 100644 index 000000000..64b5369f1 --- /dev/null +++ b/runsc/profile/profile.go @@ -0,0 +1,179 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package profile contains profiling utils. +package profile + +import ( + "os" + "runtime" + "runtime/pprof" + "runtime/trace" + + "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/control" + "gvisor.dev/gvisor/runsc/flag" +) + +// Kind is the kind of profiling to perform. +type Kind int + +const ( + // Block profile. + Block Kind = iota + // CPU profile. + CPU + // Heap profile. + Heap + // Mutex profile. + Mutex + // Trace profile. + Trace +) + +// FDArgs are the arguments that describe which profiles to enable and which +// FDs to write them to. Profiling of a given type will only be enabled if the +// corresponding FD is >=0. +type FDArgs struct { + // BlockFD is the file descriptor to write a block profile to. + // Valid if >=0. + BlockFD int + // CPUFD is the file descriptor to write a CPU profile to. + // Valid if >=0. + CPUFD int + // HeapFD is the file descriptor to write a heap profile to. + // Valid if >=0. + HeapFD int + // MutexFD is the file descriptor to write a mutex profile to. + // Valid if >=0. + MutexFD int + // TraceFD is the file descriptor to write a Go execution trace to. + // Valid if >=0. + TraceFD int +} + +// SetFromFlags sets the FDArgs from the given flags. The default value for +// each FD is -1. +func (fds *FDArgs) SetFromFlags(f *flag.FlagSet) { + f.IntVar(&fds.BlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.") + f.IntVar(&fds.CPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.") + f.IntVar(&fds.HeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.") + f.IntVar(&fds.MutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.") + f.IntVar(&fds.TraceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.") +} + +// Opts is a map of profile Kind to FD. +type Opts map[Kind]uintptr + +// ToOpts turns FDArgs into an Opts struct which can be passed to Start. +func (fds *FDArgs) ToOpts() Opts { + o := Opts{} + if fds.BlockFD >= 0 { + o[Block] = uintptr(fds.BlockFD) + } + if fds.CPUFD >= 0 { + o[CPU] = uintptr(fds.CPUFD) + } + if fds.HeapFD >= 0 { + o[Heap] = uintptr(fds.HeapFD) + } + if fds.MutexFD >= 0 { + o[Mutex] = uintptr(fds.MutexFD) + } + if fds.TraceFD >= 0 { + o[Trace] = uintptr(fds.TraceFD) + } + return o +} + +// Start starts profiling for the given Kinds in opts, and writes the profile +// data to the corresponding FDs in opts. It returns a function which will stop +// profiling. +func Start(opts Opts) func() { + var onStopProfiling []func() + stopProfiling := func() { + for _, f := range onStopProfiling { + f() + } + } + + if fd, ok := opts[Block]; ok { + log.Infof("Block profiling enabled") + file := os.NewFile(fd, "profile-block") + + runtime.SetBlockProfileRate(control.DefaultBlockProfileRate) + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("block").WriteTo(file, 0); err != nil { + log.Warningf("Error writing block profile: %v", err) + } + file.Close() + runtime.SetBlockProfileRate(0) + log.Infof("Block profiling stopped") + }) + } + + if fd, ok := opts[CPU]; ok { + log.Infof("CPU profiling enabled") + file := os.NewFile(fd, "profile-cpu") + + pprof.StartCPUProfile(file) + onStopProfiling = append(onStopProfiling, func() { + pprof.StopCPUProfile() + file.Close() + log.Infof("CPU profiling stopped") + }) + } + + if fd, ok := opts[Heap]; ok { + log.Infof("Heap profiling enabled") + file := os.NewFile(fd, "profile-heap") + + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil { + log.Warningf("Error writing heap profile: %v", err) + } + file.Close() + log.Infof("Heap profiling stopped") + }) + } + + if fd, ok := opts[Mutex]; ok { + log.Infof("Mutex profiling enabled") + file := os.NewFile(fd, "profile-mutex") + + prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate) + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil { + log.Warningf("Error writing mutex profile: %v", err) + } + file.Close() + runtime.SetMutexProfileFraction(prev) + log.Infof("Mutex profiling stopped") + }) + } + + if fd, ok := opts[Trace]; ok { + log.Infof("Tracing enabled") + file := os.NewFile(fd, "trace") + + trace.Start(file) + onStopProfiling = append(onStopProfiling, func() { + trace.Stop() + file.Close() + log.Infof("Tracing stopped") + }) + } + + return stopProfiling +}