github.com/netdata/go.d.plugin@v0.58.1/modules/docker/collect.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package docker
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/filters"
    12  )
    13  
    14  func (d *Docker) collect() (map[string]int64, error) {
    15  	if d.client == nil {
    16  		client, err := d.newClient(d.Config)
    17  		if err != nil {
    18  			return nil, err
    19  		}
    20  		d.client = client
    21  	}
    22  
    23  	if !d.verNegotiated {
    24  		d.verNegotiated = true
    25  		d.negotiateAPIVersion()
    26  	}
    27  
    28  	defer func() { _ = d.client.Close() }()
    29  
    30  	mx := make(map[string]int64)
    31  
    32  	if err := d.collectInfo(mx); err != nil {
    33  		return nil, err
    34  	}
    35  	if err := d.collectImages(mx); err != nil {
    36  		return nil, err
    37  	}
    38  	if err := d.collectContainers(mx); err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	return mx, nil
    43  }
    44  
    45  func (d *Docker) collectInfo(mx map[string]int64) error {
    46  	ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
    47  	defer cancel()
    48  
    49  	info, err := d.client.Info(ctx)
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	mx["containers_state_running"] = int64(info.ContainersRunning)
    55  	mx["containers_state_paused"] = int64(info.ContainersPaused)
    56  	mx["containers_state_exited"] = int64(info.ContainersStopped)
    57  
    58  	return nil
    59  }
    60  
    61  func (d *Docker) collectImages(mx map[string]int64) error {
    62  	ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
    63  	defer cancel()
    64  
    65  	images, err := d.client.ImageList(ctx, types.ImageListOptions{})
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	mx["images_size"] = 0
    71  	mx["images_dangling"] = 0
    72  	mx["images_active"] = 0
    73  
    74  	for _, v := range images {
    75  		mx["images_size"] += v.Size
    76  		if v.Containers == 0 {
    77  			mx["images_dangling"]++
    78  		} else {
    79  			mx["images_active"]++
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  var (
    87  	containerHealthStatuses = []string{
    88  		types.Healthy,
    89  		types.Unhealthy,
    90  		types.Starting,
    91  		types.NoHealthcheck,
    92  	}
    93  	containerStates = []string{
    94  		"created",
    95  		"running",
    96  		"paused",
    97  		"restarting",
    98  		"removing",
    99  		"exited",
   100  		"dead",
   101  	}
   102  )
   103  
   104  func (d *Docker) collectContainers(mx map[string]int64) error {
   105  	containerSet := make(map[string][]types.Container)
   106  
   107  	for _, status := range containerHealthStatuses {
   108  		if err := func() error {
   109  			ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
   110  			defer cancel()
   111  
   112  			v, err := d.client.ContainerList(ctx, types.ContainerListOptions{
   113  				All:     true,
   114  				Filters: filters.NewArgs(filters.KeyValuePair{Key: "health", Value: status}),
   115  				Size:    d.CollectContainerSize,
   116  			})
   117  			if err != nil {
   118  				return err
   119  			}
   120  			containerSet[status] = v
   121  			return nil
   122  
   123  		}(); err != nil {
   124  			return err
   125  		}
   126  	}
   127  
   128  	seen := make(map[string]bool)
   129  
   130  	for _, s := range containerHealthStatuses {
   131  		mx["containers_health_status_"+s] = 0
   132  	}
   133  	mx["containers_health_status_not_running_unhealthy"] = 0
   134  
   135  	for status, containers := range containerSet {
   136  		if status != types.Unhealthy {
   137  			mx["containers_health_status_"+status] = int64(len(containers))
   138  		}
   139  
   140  		for _, cntr := range containers {
   141  			if status == types.Unhealthy {
   142  				if cntr.State == "running" {
   143  					mx["containers_health_status_"+status] += 1
   144  				} else {
   145  					mx["containers_health_status_not_running_unhealthy"] += 1
   146  				}
   147  			}
   148  
   149  			if len(cntr.Names) == 0 {
   150  				continue
   151  			}
   152  
   153  			name := strings.TrimPrefix(cntr.Names[0], "/")
   154  
   155  			seen[name] = true
   156  
   157  			if !d.containers[name] {
   158  				d.containers[name] = true
   159  				d.addContainerCharts(name, cntr.Image)
   160  			}
   161  
   162  			px := fmt.Sprintf("container_%s_", name)
   163  
   164  			for _, s := range containerHealthStatuses {
   165  				mx[px+"health_status_"+s] = 0
   166  			}
   167  			mx[px+"health_status_not_running_unhealthy"] = 0
   168  			for _, s := range containerStates {
   169  				mx[px+"state_"+s] = 0
   170  			}
   171  
   172  			if status == types.Unhealthy && cntr.State != "running" {
   173  				mx[px+"health_status_not_running_unhealthy"] += 1
   174  			} else {
   175  				mx[px+"health_status_"+status] = 1
   176  			}
   177  			mx[px+"state_"+cntr.State] = 1
   178  			mx[px+"size_rw"] = cntr.SizeRw
   179  			mx[px+"size_root_fs"] = cntr.SizeRootFs
   180  		}
   181  	}
   182  
   183  	for name := range d.containers {
   184  		if !seen[name] {
   185  			delete(d.containers, name)
   186  			d.removeContainerCharts(name)
   187  		}
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func (d *Docker) negotiateAPIVersion() {
   194  	ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
   195  	defer cancel()
   196  
   197  	d.client.NegotiateAPIVersion(ctx)
   198  }