github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/top_unix.go (about)

     1  //go:build unix
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  /*
    20     Portions from:
    21     - https://github.com/moby/moby/blob/v20.10.6/api/types/container/container_top.go
    22     - https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go
    23     Copyright (C) The Moby authors.
    24     Licensed under the Apache License, Version 2.0
    25     NOTICE: https://github.com/moby/moby/blob/v20.10.6/NOTICE
    26  */
    27  
    28  package container
    29  
    30  import (
    31  	"bytes"
    32  	"context"
    33  	"errors"
    34  	"fmt"
    35  	"io"
    36  	"os/exec"
    37  	"strings"
    38  	"text/tabwriter"
    39  
    40  	"github.com/containerd/containerd"
    41  )
    42  
    43  // containerTop was inspired from https://github.com/moby/moby/blob/v20.10.6/daemon/top_unix.go#L133-L189
    44  //
    45  // ContainerTop lists the processes running inside of the given
    46  // container by calling ps with the given args, or with the flags
    47  // "-ef" if no args are given.  An error is returned if the container
    48  // is not found, or is not running, or if there are any problems
    49  // running ps, or parsing the output.
    50  // procList *ContainerTopOKBody
    51  func containerTop(ctx context.Context, stdio io.Writer, client *containerd.Client, id string, psArgs string) error {
    52  	if psArgs == "" {
    53  		psArgs = "-ef"
    54  	}
    55  
    56  	if err := validatePSArgs(psArgs); err != nil {
    57  		return err
    58  	}
    59  
    60  	container, err := client.LoadContainer(ctx, id)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	task, err := container.Task(ctx, nil)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	status, err := task.Status(ctx)
    71  	if err != nil {
    72  		return err
    73  	}
    74  
    75  	if status.Status != containerd.Running {
    76  		return nil
    77  	}
    78  
    79  	//TO DO handle restarting case: wait for container to restart and then launch top command
    80  
    81  	procs, err := task.Pids(ctx)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	psList := make([]uint32, 0, len(procs))
    87  	for _, ps := range procs {
    88  		psList = append(psList, ps.Pid)
    89  	}
    90  
    91  	args := strings.Split(psArgs, " ")
    92  	pids := psPidsArg(psList)
    93  	output, err := exec.Command("ps", append(args, pids)...).Output()
    94  	if err != nil {
    95  		// some ps options (such as f) can't be used together with q,
    96  		// so retry without it
    97  		output, err = exec.Command("ps", args...).Output()
    98  		if err != nil {
    99  			if ee, ok := err.(*exec.ExitError); ok {
   100  				// first line of stderr shows why ps failed
   101  				line := bytes.SplitN(ee.Stderr, []byte{'\n'}, 2)
   102  				if len(line) > 0 && len(line[0]) > 0 {
   103  					return errors.New(string(line[0]))
   104  				}
   105  			}
   106  			return nil
   107  		}
   108  	}
   109  	procList, err := parsePSOutput(output, psList)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	w := tabwriter.NewWriter(stdio, 20, 1, 3, ' ', 0)
   115  	fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
   116  
   117  	for _, proc := range procList.Processes {
   118  		fmt.Fprintln(w, strings.Join(proc, "\t"))
   119  	}
   120  
   121  	return w.Flush()
   122  }