Skip to content

Commit

Permalink
fix nerdctl ps slow on heavy IO system by using goroutine
Browse files Browse the repository at this point in the history
Signed-off-by: ningmingxiao <[email protected]>
  • Loading branch information
ningmingxiao committed Nov 13, 2024
1 parent 8b814ca commit 76c2590
Showing 1 changed file with 90 additions and 62 deletions.
152 changes: 90 additions & 62 deletions pkg/cmd/container/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ package container
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"

containerd "github.com/containerd/containerd/v2/client"
Expand All @@ -40,11 +42,11 @@ import (

// List prints containers according to `options`.
func List(ctx context.Context, client *containerd.Client, options types.ContainerListOptions) ([]ListItem, error) {
containers, err := filterContainers(ctx, client, options.Filters, options.LastN, options.All)
containers, all, err := filterContainers(ctx, client, options.Filters, options.LastN, options.All)
if err != nil {
return nil, err
}
return prepareContainers(ctx, client, containers, options)
return prepareContainers(ctx, client, containers, options, all)
}

// filterContainers returns containers matching the filters.
Expand All @@ -53,17 +55,16 @@ func List(ctx context.Context, client *containerd.Client, options types.Containe
// - all means showing all containers (default shows just running).
// - lastN means only showing n last created containers (includes all states). Non-positive values are ignored.
// In other words, if lastN is positive, all will be set to true.
func filterContainers(ctx context.Context, client *containerd.Client, filters []string, lastN int, all bool) ([]containerd.Container, error) {
func filterContainers(ctx context.Context, client *containerd.Client, filters []string, lastN int, all bool) ([]containerd.Container, bool, error) {
containers, err := client.Containers(ctx)
if err != nil {
return nil, err
return nil, false, err
}
filterCtx, err := foldContainerFilters(ctx, containers, filters)
if err != nil {
return nil, err
return nil, false, err
}
containers = filterCtx.MatchesFilters(ctx)

sort.Slice(containers, func(i, j int) bool {
infoI, _ := containers[i].Info(ctx, containerd.WithoutRefreshedMetadata)
infoJ, _ := containers[j].Info(ctx, containerd.WithoutRefreshedMetadata)
Expand All @@ -76,19 +77,10 @@ func filterContainers(ctx context.Context, client *containerd.Client, filters []
containers = containers[:lastN]
}
}

if all || filterCtx.all {
return containers, nil
}

var upContainers []containerd.Container
for _, c := range containers {
cStatus := formatter.ContainerStatus(ctx, c)
if strings.HasPrefix(cStatus, "Up") {
upContainers = append(upContainers, c)
}
return containers, true, nil
}
return upContainers, nil
return containers, false, nil
}

type ListItem struct {
Expand All @@ -112,60 +104,96 @@ func (x *ListItem) Label(s string) string {
return x.LabelsMap[s]
}

func prepareContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container, options types.ContainerListOptions) ([]ListItem, error) {
listItems := make([]ListItem, len(containers))
func prepareContainers(ctx context.Context, client *containerd.Client, containers []containerd.Container, options types.ContainerListOptions, all bool) ([]ListItem, error) {
var listItems []ListItem
snapshottersCache := map[string]snapshots.Snapshotter{}
for i, c := range containers {
info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
if errdefs.IsNotFound(err) {
log.G(ctx).Warn(err)
continue
var wg sync.WaitGroup
var mu sync.Mutex
errChan := make(chan error, len(containers))
for _, c := range containers {
wg.Add(1)
go func(ctx context.Context, c containerd.Container) {
defer wg.Done()
info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
if err != nil {
if errdefs.IsNotFound(err) {
log.G(ctx).Warn(err)
}
errChan <- err
}
return nil, err
}
spec, err := c.Spec(ctx)
if err != nil {
if errdefs.IsNotFound(err) {
log.G(ctx).Warn(err)
continue
spec, err := c.Spec(ctx)
if err != nil {
if errdefs.IsNotFound(err) {
log.G(ctx).Warn(err)
}
errChan <- err
}
return nil, err
}
id := c.ID()
if options.Truncate && len(id) > 12 {
id = id[:12]
}
li := ListItem{
Command: formatter.InspectContainerCommand(spec, options.Truncate, true),
CreatedAt: info.CreatedAt,
ID: id,
Image: info.Image,
Platform: info.Labels[labels.Platform],
Names: containerutil.GetContainerName(info.Labels),
Ports: formatter.FormatPorts(info.Labels),
Status: formatter.ContainerStatus(ctx, c),
Runtime: info.Runtime.Name,
Labels: formatter.FormatLabels(info.Labels),
LabelsMap: info.Labels,
}
if options.Size {
snapshotter, ok := snapshottersCache[info.Snapshotter]
if !ok {
snapshottersCache[info.Snapshotter] = containerdutil.SnapshotService(client, info.Snapshotter)
snapshotter = snapshottersCache[info.Snapshotter]
id := c.ID()
if options.Truncate && len(id) > 12 {
id = id[:12]
}
containerSize, err := getContainerSize(ctx, snapshotter, info.SnapshotKey)
if err != nil {
return nil, err
cStatus := formatter.ContainerStatus(ctx, c)
// only show Up status container
if all == false {

Check failure on line 137 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux |

S1002: should omit comparison to bool constant, can be simplified to `!all` (gosimple)

Check failure on line 137 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

S1002: should omit comparison to bool constant, can be simplified to `!all` (gosimple)

Check failure on line 137 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux | go-canary

S1002: should omit comparison to bool constant, can be simplified to `!all` (gosimple)
if strings.HasPrefix(cStatus, "Up") == false {

Check failure on line 138 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux |

S1002: should omit comparison to bool constant, can be simplified to `!strings.HasPrefix(cStatus, "Up")` (gosimple)

Check failure on line 138 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

S1002: should omit comparison to bool constant, can be simplified to `!strings.HasPrefix(cStatus, "Up")` (gosimple)

Check failure on line 138 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux | go-canary

S1002: should omit comparison to bool constant, can be simplified to `!strings.HasPrefix(cStatus, "Up")` (gosimple)
return
}
}
li.Size = containerSize
}
listItems[i] = li
li := ListItem{
Command: formatter.InspectContainerCommand(spec, options.Truncate, true),
CreatedAt: info.CreatedAt,
ID: id,
Image: info.Image,
Platform: info.Labels[labels.Platform],
Names: containerutil.GetContainerName(info.Labels),
Ports: formatter.FormatPorts(info.Labels),
Status: formatter.ContainerStatus(ctx, c),
Runtime: info.Runtime.Name,
Labels: formatter.FormatLabels(info.Labels),
LabelsMap: info.Labels,
}
if options.Size {
snapshotter, ok := snapshottersCache[info.Snapshotter]
if !ok {
snapshottersCache[info.Snapshotter] = containerdutil.SnapshotService(client, info.Snapshotter)
snapshotter = snapshottersCache[info.Snapshotter]
}
containerSize, err := getContainerSize(ctx, snapshotter, info.SnapshotKey)
if err != nil {
errChan <- err
}
li.Size = containerSize
}
mu.Lock()
listItems = append(listItems, li)
mu.Unlock()
}(ctx, c)
}
wg.Wait()
close(errChan)
var errs []error

Check failure on line 174 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux |

Consider pre-allocating `errs` (prealloc)

Check failure on line 174 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

Consider pre-allocating `errs` (prealloc)

Check failure on line 174 in pkg/cmd/container/list.go

View workflow job for this annotation

GitHub Actions / go | linux | go-canary

Consider pre-allocating `errs` (prealloc)
for err := range errChan {
errs = append(errs, err)
}
if len(errs) > 0 {
return nil, combineErrors(errs)
}
return listItems, nil
}

func combineErrors(errs []error) error {
var errMsgs []string
for _, err := range errs {
if err != nil {
errMsgs = append(errMsgs, err.Error())
}
}
if len(errMsgs) > 0 {
return errors.New(strings.Join(errMsgs, "; "))
}
return nil
}

func getContainerNetworks(containerLables map[string]string) []string {
var networks []string
if names, ok := containerLables[labels.Networks]; ok {
Expand Down

0 comments on commit 76c2590

Please sign in to comment.