github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/test/dockerutil/profile.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dockerutil 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "time" 24 25 "golang.org/x/sys/unix" 26 ) 27 28 // profile represents profile-like operations on a container. 29 // 30 // It is meant to be added to containers such that the container type calls 31 // the profile during its lifecycle. Standard implementations are below. 32 33 // profile is for running profiles with 'runsc debug'. 34 type profile struct { 35 BasePath string 36 Types []string 37 Duration time.Duration 38 cmd *exec.Cmd 39 } 40 41 // profileInit initializes a profile object, if required. 42 // 43 // N.B. The profiling filename initialized here will use the *image* 44 // name, and not the unique container name. This is intentional. Most 45 // of the time, profiling will be used for benchmarks. Benchmarks will 46 // be run iteratively until a sufficiently large N is reached. It is 47 // useful in this context to overwrite previous runs, and generate a 48 // single profile result for the final test. 49 func (c *Container) profileInit(image string) { 50 if !*pprofBlock && !*pprofCPU && !*pprofMutex && !*pprofHeap { 51 return // Nothing to do. 52 } 53 c.profile = &profile{ 54 BasePath: filepath.Join(*pprofBaseDir, c.runtime, c.logger.Name(), image), 55 Duration: *pprofDuration, 56 } 57 if *pprofCPU { 58 c.profile.Types = append(c.profile.Types, "cpu") 59 } 60 if *pprofHeap { 61 c.profile.Types = append(c.profile.Types, "heap") 62 } 63 if *pprofMutex { 64 c.profile.Types = append(c.profile.Types, "mutex") 65 } 66 if *pprofBlock { 67 c.profile.Types = append(c.profile.Types, "block") 68 } 69 } 70 71 // createProcess creates the collection process. 72 func (p *profile) createProcess(c *Container) error { 73 // Ensure our directory exists. 74 if err := os.MkdirAll(p.BasePath, 0755); err != nil { 75 return err 76 } 77 78 // Find the runtime to invoke. 79 path, err := RuntimePath() 80 if err != nil { 81 return fmt.Errorf("failed to get runtime path: %v", err) 82 } 83 84 // The root directory of this container's runtime. 85 rootDir := fmt.Sprintf("/var/run/docker/runtime-%s/moby", c.runtime) 86 if _, err := os.Stat(rootDir); os.IsNotExist(err) { 87 // In docker v20+, due to https://github.com/moby/moby/issues/42345 the 88 // rootDir seems to always be the following. 89 rootDir = "/var/run/docker/runtime-runc/moby" 90 } 91 92 // Format is `runsc --root=rootDir debug --profile-*=file --duration=24h containerID`. 93 args := []string{fmt.Sprintf("--root=%s", rootDir), "debug"} 94 for _, profileArg := range p.Types { 95 outputPath := filepath.Join(p.BasePath, fmt.Sprintf("%s.pprof", profileArg)) 96 args = append(args, fmt.Sprintf("--profile-%s=%s", profileArg, outputPath)) 97 } 98 args = append(args, fmt.Sprintf("--duration=%s", p.Duration)) // Or until container exits. 99 args = append(args, fmt.Sprintf("--delay=%s", p.Duration)) // Ditto. 100 args = append(args, c.ID()) 101 102 // Best effort wait until container is running. 103 for now := time.Now(); time.Since(now) < 5*time.Second; { 104 if status, err := c.Status(context.Background()); err != nil { 105 return fmt.Errorf("failed to get status with: %v", err) 106 } else if status.Running { 107 break 108 } 109 time.Sleep(100 * time.Millisecond) 110 } 111 p.cmd = exec.Command(path, args...) 112 p.cmd.Stderr = os.Stderr // Pass through errors. 113 if err := p.cmd.Start(); err != nil { 114 return fmt.Errorf("start process failed: %v", err) 115 } 116 117 return nil 118 } 119 120 // killProcess kills the process, if running. 121 func (p *profile) killProcess() error { 122 if p.cmd != nil && p.cmd.Process != nil { 123 return p.cmd.Process.Signal(unix.SIGTERM) 124 } 125 return nil 126 } 127 128 // waitProcess waits for the process, if running. 129 func (p *profile) waitProcess() error { 130 defer func() { p.cmd = nil }() 131 if p.cmd != nil { 132 return p.cmd.Wait() 133 } 134 return nil 135 } 136 137 // Start is called when profiling is started. 138 func (p *profile) Start(c *Container) error { 139 return p.createProcess(c) 140 } 141 142 // Stop is called when profiling is started. 143 func (p *profile) Stop(c *Container) error { 144 killErr := p.killProcess() 145 waitErr := p.waitProcess() 146 if waitErr != nil && killErr != nil { 147 return killErr 148 } 149 return waitErr // Ignore okay wait, err kill. 150 }