Skip to content

Commit

Permalink
stop grabbing container locks during ps
Browse files Browse the repository at this point in the history
Container queries are now served from the consistent in-memory db, and
don't need to grab a lock on every container being listed.

Signed-off-by: Fabio Kung <[email protected]>
  • Loading branch information
fabiokung committed Jun 23, 2017
1 parent eed4c7b commit 8e425eb
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 125 deletions.
189 changes: 66 additions & 123 deletions daemon/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/container"
"github.com/docker/docker/image"
"github.com/docker/docker/volume"
Expand Down Expand Up @@ -47,7 +46,7 @@ type iterationAction int

// containerReducer represents a reducer for a container.
// Returns the object to serialize by the api.
type containerReducer func(*container.Container, *listContext) (*types.Container, error)
type containerReducer func(*container.Snapshot, *listContext) (*types.Container, error)

const (
// includeContainer is the action to include a container in the reducer.
Expand Down Expand Up @@ -83,9 +82,9 @@ type listContext struct {
exitAllowed []int

// beforeFilter is a filter to ignore containers that appear before the one given
beforeFilter *container.Container
beforeFilter *container.Snapshot
// sinceFilter is a filter to stop the filtering when the iterator arrive to the given container
sinceFilter *container.Container
sinceFilter *container.Snapshot

// taskFilter tells if we should filter based on wether a container is part of a task
taskFilter bool
Expand All @@ -102,7 +101,7 @@ type listContext struct {
}

// byContainerCreated is a temporary type used to sort a list of containers by creation time.
type byContainerCreated []*container.Container
type byContainerCreated []container.Snapshot

func (r byContainerCreated) Len() int { return len(r) }
func (r byContainerCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
Expand All @@ -115,15 +114,17 @@ func (daemon *Daemon) Containers(config *types.ContainerListOptions) ([]*types.C
return daemon.reduceContainers(config, daemon.transformContainer)
}

func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Container {
func (daemon *Daemon) filterByNameIDMatches(view *container.View, ctx *listContext) ([]container.Snapshot, error) {
idSearch := false
names := ctx.filters.Get("name")
ids := ctx.filters.Get("id")
if len(names)+len(ids) == 0 {
// if name or ID filters are not in use, return to
// standard behavior of walking the entire container
// list from the daemon's in-memory store
return daemon.List()
all, err := view.All()
sort.Sort(sort.Reverse(byContainerCreated(all)))
return all, err
}

// idSearch will determine if we limit name matching to the IDs
Expand Down Expand Up @@ -158,38 +159,46 @@ func (daemon *Daemon) filterByNameIDMatches(ctx *listContext) []*container.Conta
}
}

cntrs := make([]*container.Container, 0, len(matches))
cntrs := make([]container.Snapshot, 0, len(matches))
for id := range matches {
if c := daemon.containers.Get(id); c != nil {
cntrs = append(cntrs, c)
c, err := view.Get(id)
if err != nil {
return nil, err
}
if c != nil {
cntrs = append(cntrs, *c)
}
}

// Restore sort-order after filtering
// Created gives us nanosec resolution for sorting
sort.Sort(sort.Reverse(byContainerCreated(cntrs)))

return cntrs
return cntrs, nil
}

// reduceContainers parses the user's filtering options and generates the list of containers to return based on a reducer.
func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reducer containerReducer) ([]*types.Container, error) {
var (
view = daemon.containersReplica.Snapshot()
containers = []*types.Container{}
)

ctx, err := daemon.foldFilter(config)
ctx, err := daemon.foldFilter(view, config)
if err != nil {
return nil, err
}

// fastpath to only look at a subset of containers if specific name
// or ID matches were provided by the user--otherwise we potentially
// end up locking and querying many more containers than intended
containerList := daemon.filterByNameIDMatches(ctx)
// end up querying many more containers than intended
containerList, err := daemon.filterByNameIDMatches(view, ctx)
if err != nil {
return nil, err
}

for _, container := range containerList {
t, err := daemon.reducePsContainer(container, ctx, reducer)
for i := range containerList {
t, err := daemon.reducePsContainer(&containerList[i], ctx, reducer)
if err != nil {
if err != errStopIteration {
return nil, err
Expand All @@ -206,23 +215,17 @@ func (daemon *Daemon) reduceContainers(config *types.ContainerListOptions, reduc
}

// reducePsContainer is the basic representation for a container as expected by the ps command.
func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *listContext, reducer containerReducer) (*types.Container, error) {
container.Lock()

func (daemon *Daemon) reducePsContainer(container *container.Snapshot, ctx *listContext, reducer containerReducer) (*types.Container, error) {
// filter containers to return
action := includeContainerInList(container, ctx)
switch action {
switch includeContainerInList(container, ctx) {
case excludeContainer:
container.Unlock()
return nil, nil
case stopIteration:
container.Unlock()
return nil, errStopIteration
}

// transform internal container struct into api structs
newC, err := reducer(container, ctx)
container.Unlock()
if err != nil {
return nil, err
}
Expand All @@ -237,7 +240,7 @@ func (daemon *Daemon) reducePsContainer(container *container.Container, ctx *lis
}

// foldFilter generates the container filter based on the user's filtering options.
func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listContext, error) {
func (daemon *Daemon) foldFilter(view *container.View, config *types.ContainerListOptions) (*listContext, error) {
psFilters := config.Filters

if err := psFilters.Validate(acceptedPsFilterTags); err != nil {
Expand Down Expand Up @@ -294,18 +297,18 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
return nil, err
}

var beforeContFilter, sinceContFilter *container.Container
var beforeContFilter, sinceContFilter *container.Snapshot

err = psFilters.WalkValues("before", func(value string) error {
beforeContFilter, err = daemon.GetContainer(value)
beforeContFilter, err = view.Get(value)
return err
})
if err != nil {
return nil, err
}

err = psFilters.WalkValues("since", func(value string) error {
sinceContFilter, err = daemon.GetContainer(value)
sinceContFilter, err = view.Get(value)
return err
})
if err != nil {
Expand Down Expand Up @@ -383,7 +386,7 @@ func portOp(key string, filter map[nat.Port]bool) func(value string) error {

// includeContainerInList decides whether a container should be included in the output or not based in the filter.
// It also decides if the iteration should be stopped or not.
func includeContainerInList(container *container.Container, ctx *listContext) iterationAction {
func includeContainerInList(container *container.Snapshot, ctx *listContext) iterationAction {
// Do not include container if it's in the list before the filter container.
// Set the filter container to nil to include the rest of containers after this one.
if ctx.beforeFilter != nil {
Expand Down Expand Up @@ -422,7 +425,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
}

// Do not include container if any of the labels don't match
if !ctx.filters.MatchKVList("label", container.Config.Labels) {
if !ctx.filters.MatchKVList("label", container.Labels) {
return excludeContainer
}

Expand All @@ -440,7 +443,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
if len(ctx.exitAllowed) > 0 {
shouldSkip := true
for _, code := range ctx.exitAllowed {
if code == container.ExitCode() && !container.Running && !container.StartedAt.IsZero() {
if code == container.ExitCode && !container.Running && !container.StartedAt.IsZero() {
shouldSkip = false
break
}
Expand All @@ -451,28 +454,34 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
}

// Do not include container if its status doesn't match the filter
if !ctx.filters.Match("status", container.State.StateString()) {
if !ctx.filters.Match("status", container.State) {
return excludeContainer
}

// Do not include container if its health doesn't match the filter
if !ctx.filters.ExactMatch("health", container.State.HealthString()) {
if !ctx.filters.ExactMatch("health", container.Health) {
return excludeContainer
}

if ctx.filters.Include("volume") {
volumesByName := make(map[string]*volume.MountPoint)
for _, m := range container.MountPoints {
volumesByName := make(map[string]types.MountPoint)
for _, m := range container.Mounts {
if m.Name != "" {
volumesByName[m.Name] = m
} else {
volumesByName[m.Source] = m
}
}
volumesByDestination := make(map[string]types.MountPoint)
for _, m := range container.Mounts {
if m.Destination != "" {
volumesByDestination[m.Destination] = m
}
}

volumeExist := fmt.Errorf("volume mounted in container")
err := ctx.filters.WalkValues("volume", func(value string) error {
if _, exist := container.MountPoints[value]; exist {
if _, exist := volumesByDestination[value]; exist {
return volumeExist
}
if _, exist := volumesByName[value]; exist {
Expand All @@ -489,7 +498,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
if len(ctx.images) == 0 {
return excludeContainer
}
if !ctx.images[container.ImageID] {
if !ctx.images[image.ID(container.ImageID)] {
return excludeContainer
}
}
Expand All @@ -501,7 +510,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
return networkExist
}
for _, nw := range container.NetworkSettings.Networks {
if nw.EndpointSettings == nil {
if nw == nil {
continue
}
if strings.HasPrefix(nw.NetworkID, value) {
Expand All @@ -518,7 +527,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
if len(ctx.publish) > 0 {
shouldSkip := true
for port := range ctx.publish {
if _, ok := container.HostConfig.PortBindings[port]; ok {
if _, ok := container.PublishPorts[port]; ok {
shouldSkip = false
break
}
Expand All @@ -531,7 +540,7 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
if len(ctx.expose) > 0 {
shouldSkip := true
for port := range ctx.expose {
if _, ok := container.Config.ExposedPorts[port]; ok {
if _, ok := container.ExposedPorts[port]; ok {
shouldSkip = false
break
}
Expand All @@ -545,104 +554,38 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
}

// transformContainer generates the container type expected by the docker ps command.
func (daemon *Daemon) transformContainer(container *container.Container, ctx *listContext) (*types.Container, error) {
func (daemon *Daemon) transformContainer(container *container.Snapshot, ctx *listContext) (*types.Container, error) {
newC := &types.Container{
ID: container.ID,
Names: ctx.names[container.ID],
ImageID: container.ImageID.String(),
ID: container.ID,
Names: ctx.names[container.ID],
ImageID: container.ImageID,
Command: container.Command,
Created: container.Created.Unix(),
State: container.State,
Status: container.Status,
NetworkSettings: &container.NetworkSettings,
Ports: container.Ports,
Labels: container.Labels,
Mounts: container.Mounts,
}
if newC.Names == nil {
// Dead containers will often have no name, so make sure the response isn't null
newC.Names = []string{}
}
newC.HostConfig.NetworkMode = container.HostConfig.NetworkMode

image := container.Config.Image // if possible keep the original ref
if image != container.ImageID.String() {
image := container.Image // if possible keep the original ref
if image != container.ImageID {
id, _, err := daemon.GetImageIDAndPlatform(image)
if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
return nil, err
}
if err != nil || id != container.ImageID {
image = container.ImageID.String()
if err != nil || id.String() != container.ImageID {
image = container.ImageID
}
}
newC.Image = image

if len(container.Args) > 0 {
args := []string{}
for _, arg := range container.Args {
if strings.Contains(arg, " ") {
args = append(args, fmt.Sprintf("'%s'", arg))
} else {
args = append(args, arg)
}
}
argsAsString := strings.Join(args, " ")

newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString)
} else {
newC.Command = container.Path
}
newC.Created = container.Created.Unix()
newC.State = container.State.StateString()
newC.Status = container.State.String()
newC.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode)
// copy networks to avoid races
networks := make(map[string]*networktypes.EndpointSettings)
for name, network := range container.NetworkSettings.Networks {
if network == nil || network.EndpointSettings == nil {
continue
}
networks[name] = &networktypes.EndpointSettings{
EndpointID: network.EndpointID,
Gateway: network.Gateway,
IPAddress: network.IPAddress,
IPPrefixLen: network.IPPrefixLen,
IPv6Gateway: network.IPv6Gateway,
GlobalIPv6Address: network.GlobalIPv6Address,
GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
MacAddress: network.MacAddress,
NetworkID: network.NetworkID,
}
if network.IPAMConfig != nil {
networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{
IPv4Address: network.IPAMConfig.IPv4Address,
IPv6Address: network.IPAMConfig.IPv6Address,
}
}
}
newC.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks}

newC.Ports = []types.Port{}
for port, bindings := range container.NetworkSettings.Ports {
p, err := nat.ParsePort(port.Port())
if err != nil {
return nil, err
}
if len(bindings) == 0 {
newC.Ports = append(newC.Ports, types.Port{
PrivatePort: uint16(p),
Type: port.Proto(),
})
continue
}
for _, binding := range bindings {
h, err := nat.ParsePort(binding.HostPort)
if err != nil {
return nil, err
}
newC.Ports = append(newC.Ports, types.Port{
PrivatePort: uint16(p),
PublicPort: uint16(h),
Type: port.Proto(),
IP: binding.HostIP,
})
}
}

newC.Labels = container.Config.Labels
newC.Mounts = addMountPoints(container)

return newC, nil
}

Expand Down
Loading

0 comments on commit 8e425eb

Please sign in to comment.