master
go 211 lines 4.51 KB
Raw
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 typesContainer "github.com/docker/docker/api/types/container"
12 "github.com/docker/docker/api/types/filters"
13 typesImage "github.com/docker/docker/api/types/image"
14 )
15
16 func (c *Collector) collect() (map[string]int64, error) {
17 if c.client == nil {
18 client, err := c.newClient(c.Config)
19 if err != nil {
20 return nil, err
21 }
22 c.client = client
23 }
24
25 if !c.verNegotiated {
26 c.verNegotiated = true
27 c.negotiateAPIVersion()
28 }
29
30 mx := make(map[string]int64)
31
32 if err := c.collectInfo(mx); err != nil {
33 return nil, err
34 }
35 if err := c.collectImages(mx); err != nil {
36 return nil, err
37 }
38 if err := c.collectContainers(mx); err != nil {
39 return nil, err
40 }
41
42 return mx, nil
43 }
44
45 func (c *Collector) collectInfo(mx map[string]int64) error {
46 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
47 defer cancel()
48
49 info, err := c.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 (c *Collector) collectImages(mx map[string]int64) error {
62 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
63 defer cancel()
64
65 images, err := c.client.ImageList(ctx, typesImage.ListOptions{})
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 (c *Collector) collectContainers(mx map[string]int64) error {
105 containerSet := make(map[string][]typesContainer.Summary)
106
107 for _, status := range containerHealthStatuses {
108 if err := func() error {
109 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
110 defer cancel()
111
112 containers, err := c.client.ContainerList(ctx, typesContainer.ListOptions{
113 All: true,
114 Filters: filters.NewArgs(filters.KeyValuePair{Key: "health", Value: status}),
115 Size: c.CollectContainerSize,
116 })
117 if err != nil {
118 return err
119 }
120 containerSet[status] = containers
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 hasIgnoreLabel(cntr) {
150 continue
151 }
152
153 if len(cntr.Names) == 0 {
154 continue
155 }
156
157 name := strings.TrimPrefix(cntr.Names[0], "/")
158
159 if c.cntrSr != nil && !c.cntrSr.MatchString(name) {
160 continue
161 }
162
163 seen[name] = true
164
165 if !c.containers[name] {
166 c.containers[name] = true
167 c.addContainerCharts(name, cntr.Image)
168 }
169
170 px := fmt.Sprintf("container_%s_", name)
171
172 for _, s := range containerHealthStatuses {
173 mx[px+"health_status_"+s] = 0
174 }
175 mx[px+"health_status_not_running_unhealthy"] = 0
176 for _, s := range containerStates {
177 mx[px+"state_"+s] = 0
178 }
179
180 if status == types.Unhealthy && cntr.State != "running" {
181 mx[px+"health_status_not_running_unhealthy"] += 1
182 } else {
183 mx[px+"health_status_"+status] = 1
184 }
185 mx[px+"state_"+cntr.State] = 1
186 mx[px+"size_rw"] = cntr.SizeRw
187 mx[px+"size_root_fs"] = cntr.SizeRootFs
188 }
189 }
190
191 for name := range c.containers {
192 if !seen[name] {
193 delete(c.containers, name)
194 c.removeContainerCharts(name)
195 }
196 }
197
198 return nil
199 }
200
201 func (c *Collector) negotiateAPIVersion() {
202 ctx, cancel := context.WithTimeout(context.Background(), c.Timeout.Duration())
203 defer cancel()
204
205 c.client.NegotiateAPIVersion(ctx)
206 }
207
208 func hasIgnoreLabel(cntr typesContainer.Summary) bool {
209 v := cntr.Labels["netdata.cloud/ignore"]
210 return strings.EqualFold(v, "true") || strings.EqualFold(v, "yes")
211 }