github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/container_top_linux.go (about) 1 // +build linux 2 3 package libpod 4 5 import ( 6 "bufio" 7 "fmt" 8 "os" 9 "strconv" 10 "strings" 11 12 "github.com/containers/podman/v3/libpod/define" 13 "github.com/containers/podman/v3/pkg/rootless" 14 "github.com/containers/psgo" 15 "github.com/google/shlex" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 // Top gathers statistics about the running processes in a container. It returns a 21 // []string for output 22 func (c *Container) Top(descriptors []string) ([]string, error) { 23 if c.config.NoCgroups { 24 return nil, errors.Wrapf(define.ErrNoCgroups, "cannot run top on container %s as it did not create a cgroup", c.ID()) 25 } 26 27 conStat, err := c.State() 28 if err != nil { 29 return nil, errors.Wrapf(err, "unable to look up state for %s", c.ID()) 30 } 31 if conStat != define.ContainerStateRunning { 32 return nil, errors.Errorf("top can only be used on running containers") 33 } 34 35 // Also support comma-separated input. 36 psgoDescriptors := []string{} 37 for _, d := range descriptors { 38 for _, s := range strings.Split(d, ",") { 39 if s != "" { 40 psgoDescriptors = append(psgoDescriptors, s) 41 } 42 } 43 } 44 45 // If we encountered an ErrUnknownDescriptor error, fallback to executing 46 // ps(1). This ensures backwards compatibility to users depending on ps(1) 47 // and makes sure we're ~compatible with docker. 48 output, psgoErr := c.GetContainerPidInformation(psgoDescriptors) 49 if psgoErr == nil { 50 return output, nil 51 } 52 if !errors.Is(psgoErr, psgo.ErrUnknownDescriptor) { 53 return nil, psgoErr 54 } 55 56 // Note that the descriptors to ps(1) must be shlexed (see #12452). 57 psDescriptors := []string{} 58 for _, d := range descriptors { 59 shSplit, err := shlex.Split(d) 60 if err != nil { 61 return nil, fmt.Errorf("parsing ps args: %v", err) 62 } 63 for _, s := range shSplit { 64 if s != "" { 65 psDescriptors = append(psDescriptors, s) 66 } 67 } 68 } 69 70 output, err = c.execPS(psDescriptors) 71 if err != nil { 72 return nil, errors.Wrapf(err, "error executing ps(1) in the container") 73 } 74 75 // Trick: filter the ps command from the output instead of 76 // checking/requiring PIDs in the output. 77 filtered := []string{} 78 cmd := strings.Join(descriptors, " ") 79 for _, line := range output { 80 if !strings.Contains(line, cmd) { 81 filtered = append(filtered, line) 82 } 83 } 84 85 return filtered, nil 86 } 87 88 // GetContainerPidInformation returns process-related data of all processes in 89 // the container. The output data can be controlled via the `descriptors` 90 // argument which expects format descriptors and supports all AIXformat 91 // descriptors of ps (1) plus some additional ones to for instance inspect the 92 // set of effective capabilities. Each element in the returned string slice 93 // is a tab-separated string. 94 // 95 // For more details, please refer to github.com/containers/psgo. 96 func (c *Container) GetContainerPidInformation(descriptors []string) ([]string, error) { 97 pid := strconv.Itoa(c.state.PID) 98 // TODO: psgo returns a [][]string to give users the ability to apply 99 // filters on the data. We need to change the API here 100 // to return a [][]string if we want to make use of 101 // filtering. 102 opts := psgo.JoinNamespaceOpts{FillMappings: rootless.IsRootless()} 103 104 psgoOutput, err := psgo.JoinNamespaceAndProcessInfoWithOptions(pid, descriptors, &opts) 105 if err != nil { 106 return nil, err 107 } 108 res := []string{} 109 for _, out := range psgoOutput { 110 res = append(res, strings.Join(out, "\t")) 111 } 112 return res, nil 113 } 114 115 // execPS executes ps(1) with the specified args in the container. 116 func (c *Container) execPS(args []string) ([]string, error) { 117 rPipe, wPipe, err := os.Pipe() 118 if err != nil { 119 return nil, err 120 } 121 defer wPipe.Close() 122 defer rPipe.Close() 123 124 rErrPipe, wErrPipe, err := os.Pipe() 125 if err != nil { 126 return nil, err 127 } 128 defer wErrPipe.Close() 129 defer rErrPipe.Close() 130 131 streams := new(define.AttachStreams) 132 streams.OutputStream = wPipe 133 streams.ErrorStream = wErrPipe 134 streams.AttachOutput = true 135 streams.AttachError = true 136 137 stdout := []string{} 138 go func() { 139 scanner := bufio.NewScanner(rPipe) 140 for scanner.Scan() { 141 stdout = append(stdout, scanner.Text()) 142 } 143 }() 144 stderr := []string{} 145 go func() { 146 scanner := bufio.NewScanner(rErrPipe) 147 for scanner.Scan() { 148 stderr = append(stderr, scanner.Text()) 149 } 150 }() 151 152 cmd := append([]string{"ps"}, args...) 153 config := new(ExecConfig) 154 config.Command = cmd 155 ec, err := c.Exec(config, streams, nil) 156 if err != nil { 157 return nil, err 158 } else if ec != 0 { 159 return nil, errors.Errorf("Runtime failed with exit status: %d and output: %s", ec, strings.Join(stderr, " ")) 160 } 161 162 if logrus.GetLevel() >= logrus.DebugLevel { 163 // If we're running in debug mode or higher, we might want to have a 164 // look at stderr which includes debug logs from conmon. 165 for _, log := range stderr { 166 logrus.Debugf("%s", log) 167 } 168 } 169 170 return stdout, nil 171 }