Skip to content

Commit dbcbda1

Browse files
committed
Initial commit
0 parents  commit dbcbda1

File tree

3 files changed

+333
-0
lines changed

3 files changed

+333
-0
lines changed

Dockerfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM golang:1.14
2+
3+
COPY . /go/src/docker-stats/
4+
RUN go get docker-stats/...
5+
RUN go install docker-stats
6+
7+
ENTRYPOINT ["docker-stats"]
8+
CMD ["/"]

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# docker-stats
2+
3+
Relatively minimal docker container stats exporter for Prometheus.
4+
5+
TODO: Documentation

main.go

+320
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"io/ioutil"
7+
"log"
8+
"net/http"
9+
"os"
10+
"strings"
11+
12+
"github.com/docker/docker/api/types"
13+
"github.com/docker/docker/client"
14+
"golang.org/x/sys/unix"
15+
16+
"github.com/prometheus/client_golang/prometheus"
17+
"github.com/prometheus/client_golang/prometheus/promhttp"
18+
)
19+
20+
const (
21+
containerPrefix = "container_"
22+
dataPrefix = "container_data_"
23+
)
24+
25+
var (
26+
knownContainerIDs map[string]prometheus.Labels
27+
knownContainerNetworks map[string]prometheus.Labels
28+
knownDataNames map[string]prometheus.Labels
29+
30+
pids *prometheus.GaugeVec
31+
cpuUsageUser *prometheus.GaugeVec
32+
cpuUsageKernel *prometheus.GaugeVec
33+
cpuUsageTotal *prometheus.GaugeVec
34+
memoryUsage *prometheus.GaugeVec
35+
memoryLimit *prometheus.GaugeVec
36+
37+
networkReceiveBytes *prometheus.GaugeVec
38+
networkTransmitBytes *prometheus.GaugeVec
39+
networkReceivePackets *prometheus.GaugeVec
40+
networkTransmitPackets *prometheus.GaugeVec
41+
networkReceiveErrors *prometheus.GaugeVec
42+
networkTransmitErrors *prometheus.GaugeVec
43+
networkReceiveDropped *prometheus.GaugeVec
44+
networkTransmitDropped *prometheus.GaugeVec
45+
46+
dataFree *prometheus.GaugeVec
47+
dataAvailable *prometheus.GaugeVec
48+
dataSize *prometheus.GaugeVec
49+
dataInodesFree *prometheus.GaugeVec
50+
dataInodes *prometheus.GaugeVec
51+
)
52+
53+
func setup() {
54+
containerLabels := []string{"container_id", "container_name", "compose_project", "compose_service", "container_image_id", "container_image_name"}
55+
containerNetworkLabels := append(containerLabels, "interface")
56+
dataLabels := []string{"data_name"}
57+
58+
pids = prometheus.NewGaugeVec(prometheus.GaugeOpts{
59+
Name: containerPrefix + "pids",
60+
Help: "Number of running processes in the container",
61+
}, containerLabels)
62+
cpuUsageUser = prometheus.NewGaugeVec(prometheus.GaugeOpts{
63+
Name: containerPrefix + "cpu_usage_user_seconds_total",
64+
Help: "Container CPU usage in user mode",
65+
}, containerLabels)
66+
cpuUsageKernel = prometheus.NewGaugeVec(prometheus.GaugeOpts{
67+
Name: containerPrefix + "cpu_usage_kernel_seconds_total",
68+
Help: "Container CPU usage in kernel mode",
69+
}, containerLabels)
70+
cpuUsageTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
71+
Name: containerPrefix + "cpu_usage_seconds_total",
72+
Help: "Container CPU usage",
73+
}, containerLabels)
74+
cpuUsageTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{
75+
Name: containerPrefix + "cpu_usage_seconds_total",
76+
Help: "Container CPU usage",
77+
}, containerLabels)
78+
memoryUsage = prometheus.NewGaugeVec(prometheus.GaugeOpts{
79+
Name: containerPrefix + "memory_usage_bytes",
80+
Help: "Container Memory usage",
81+
}, containerLabels)
82+
memoryLimit = prometheus.NewGaugeVec(prometheus.GaugeOpts{
83+
Name: containerPrefix + "memory_limit_bytes",
84+
Help: "Container Memory limit",
85+
}, containerLabels)
86+
87+
networkReceiveBytes = prometheus.NewGaugeVec(prometheus.GaugeOpts{
88+
Name: containerPrefix + "network_receive_bytes_total",
89+
Help: "Container network received bytes",
90+
}, containerNetworkLabels)
91+
networkTransmitBytes = prometheus.NewGaugeVec(prometheus.GaugeOpts{
92+
Name: containerPrefix + "network_transmit_bytes_total",
93+
Help: "Container network transmitted bytes",
94+
}, containerNetworkLabels)
95+
networkReceivePackets = prometheus.NewGaugeVec(prometheus.GaugeOpts{
96+
Name: containerPrefix + "network_receive_packets_total",
97+
Help: "Container network received packets",
98+
}, containerNetworkLabels)
99+
networkTransmitPackets = prometheus.NewGaugeVec(prometheus.GaugeOpts{
100+
Name: containerPrefix + "network_transmit_packets_total",
101+
Help: "Container network transmitted packets",
102+
}, containerNetworkLabels)
103+
networkReceiveErrors = prometheus.NewGaugeVec(prometheus.GaugeOpts{
104+
Name: containerPrefix + "network_receive_errors_total",
105+
Help: "Container network receive errors",
106+
}, containerNetworkLabels)
107+
networkTransmitErrors = prometheus.NewGaugeVec(prometheus.GaugeOpts{
108+
Name: containerPrefix + "network_transmit_errors_total",
109+
Help: "Container network transmit errors",
110+
}, containerNetworkLabels)
111+
networkReceiveDropped = prometheus.NewGaugeVec(prometheus.GaugeOpts{
112+
Name: containerPrefix + "network_receive_dropped_total",
113+
Help: "Container network receive drops",
114+
}, containerNetworkLabels)
115+
networkTransmitDropped = prometheus.NewGaugeVec(prometheus.GaugeOpts{
116+
Name: containerPrefix + "network_transmit_dropped_total",
117+
Help: "Container network transmit drops",
118+
}, containerNetworkLabels)
119+
120+
dataFree = prometheus.NewGaugeVec(prometheus.GaugeOpts{
121+
Name: dataPrefix + "free_bytes",
122+
Help: "Container data used bytes",
123+
}, dataLabels)
124+
dataAvailable = prometheus.NewGaugeVec(prometheus.GaugeOpts{
125+
Name: dataPrefix + "available_bytes",
126+
Help: "Container data available bytes",
127+
}, dataLabels)
128+
dataSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
129+
Name: dataPrefix + "size_bytes",
130+
Help: "Container data total bytes",
131+
}, dataLabels)
132+
dataInodesFree = prometheus.NewGaugeVec(prometheus.GaugeOpts{
133+
Name: dataPrefix + "free_inodes",
134+
Help: "Container data free inodes",
135+
}, dataLabels)
136+
dataInodes = prometheus.NewGaugeVec(prometheus.GaugeOpts{
137+
Name: dataPrefix + "inodes",
138+
Help: "Container data total inodes",
139+
}, dataLabels)
140+
141+
prometheus.MustRegister(pids)
142+
prometheus.MustRegister(cpuUsageUser)
143+
prometheus.MustRegister(cpuUsageKernel)
144+
prometheus.MustRegister(cpuUsageTotal)
145+
prometheus.MustRegister(memoryUsage)
146+
prometheus.MustRegister(memoryLimit)
147+
148+
prometheus.MustRegister(networkReceiveBytes)
149+
prometheus.MustRegister(networkTransmitBytes)
150+
prometheus.MustRegister(networkReceivePackets)
151+
prometheus.MustRegister(networkTransmitPackets)
152+
prometheus.MustRegister(networkReceiveErrors)
153+
prometheus.MustRegister(networkTransmitErrors)
154+
prometheus.MustRegister(networkReceiveDropped)
155+
prometheus.MustRegister(networkTransmitDropped)
156+
157+
prometheus.MustRegister(dataFree)
158+
prometheus.MustRegister(dataAvailable)
159+
prometheus.MustRegister(dataSize)
160+
prometheus.MustRegister(dataInodesFree)
161+
prometheus.MustRegister(dataInodes)
162+
}
163+
164+
func updateContainers(docker *client.Client) {
165+
newKnownContainerIDs := make(map[string]prometheus.Labels)
166+
newKnownContainerNetworks := make(map[string]prometheus.Labels)
167+
containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{})
168+
if err != nil {
169+
log.Print("Failed to get container list: ", err)
170+
}
171+
for _, container := range containers {
172+
resp, err := docker.ContainerStatsOneShot(context.Background(), container.ID)
173+
if err != nil {
174+
log.Print("Failed to get container stats: ", err)
175+
continue
176+
}
177+
stats := types.StatsJSON{}
178+
err = json.NewDecoder(resp.Body).Decode(&stats)
179+
if err != nil {
180+
log.Print("Failed to parse container stats: ", err)
181+
continue
182+
}
183+
labels := prometheus.Labels{
184+
"container_id": container.ID,
185+
"container_name": strings.TrimPrefix(container.Names[0], "/"),
186+
"compose_project": container.Labels["com.docker.compose.project"],
187+
"compose_service": container.Labels["com.docker.compose.service"],
188+
"container_image_id": strings.TrimPrefix(container.ImageID, "sha256:"),
189+
"container_image_name": container.Image,
190+
}
191+
resp.Body.Close()
192+
newKnownContainerIDs[container.ID] = labels
193+
194+
pids.With(labels).Set(float64(stats.PidsStats.Current))
195+
cpuUsageUser.With(labels).Set(float64(stats.CPUStats.CPUUsage.UsageInUsermode) / 1e9)
196+
cpuUsageKernel.With(labels).Set(float64(stats.CPUStats.CPUUsage.UsageInKernelmode) / 1e9)
197+
cpuUsageTotal.With(labels).Set(float64(stats.CPUStats.CPUUsage.TotalUsage) / 1e9)
198+
memoryUsage.With(labels).Set(float64(stats.MemoryStats.Usage - stats.MemoryStats.Stats["cache"]))
199+
memoryLimit.With(labels).Set(float64(stats.MemoryStats.Limit))
200+
201+
for intf, net := range stats.Networks {
202+
labels := prometheus.Labels{
203+
"container_id": container.ID,
204+
"container_name": strings.TrimPrefix(container.Names[0], "/"),
205+
"compose_project": container.Labels["com.docker.compose.project"],
206+
"compose_service": container.Labels["com.docker.compose.service"],
207+
"container_image_id": strings.TrimPrefix(container.ImageID, "sha256:"),
208+
"container_image_name": container.Image,
209+
"interface": intf,
210+
}
211+
newKnownContainerNetworks[container.ID+intf] = labels
212+
networkReceiveBytes.With(labels).Set(float64(net.RxBytes))
213+
networkTransmitBytes.With(labels).Set(float64(net.TxBytes))
214+
networkReceivePackets.With(labels).Set(float64(net.RxPackets))
215+
networkTransmitPackets.With(labels).Set(float64(net.TxPackets))
216+
networkReceiveErrors.With(labels).Set(float64(net.RxErrors))
217+
networkTransmitErrors.With(labels).Set(float64(net.TxErrors))
218+
networkReceiveDropped.With(labels).Set(float64(net.RxDropped))
219+
networkTransmitDropped.With(labels).Set(float64(net.TxDropped))
220+
}
221+
}
222+
for id, labels := range knownContainerIDs {
223+
if newKnownContainerIDs[id] == nil {
224+
pids.Delete(labels)
225+
cpuUsageUser.Delete(labels)
226+
cpuUsageKernel.Delete(labels)
227+
cpuUsageTotal.Delete(labels)
228+
memoryUsage.Delete(labels)
229+
memoryLimit.Delete(labels)
230+
}
231+
}
232+
for id, labels := range knownContainerNetworks {
233+
if newKnownContainerNetworks[id] == nil {
234+
networkReceiveBytes.Delete(labels)
235+
networkTransmitBytes.Delete(labels)
236+
networkReceivePackets.Delete(labels)
237+
networkTransmitPackets.Delete(labels)
238+
networkReceiveErrors.Delete(labels)
239+
networkTransmitErrors.Delete(labels)
240+
networkReceiveDropped.Delete(labels)
241+
networkTransmitDropped.Delete(labels)
242+
}
243+
}
244+
knownContainerIDs = newKnownContainerIDs
245+
knownContainerNetworks = newKnownContainerNetworks
246+
}
247+
248+
func updateDatas(basepath string) {
249+
newKnownDataNames := make(map[string]prometheus.Labels)
250+
var paths []string
251+
if basepath == "/" {
252+
paths = append(paths, "/")
253+
} else {
254+
files, err := ioutil.ReadDir(basepath)
255+
if err != nil {
256+
log.Print("Failed to get data names: ", err)
257+
return
258+
}
259+
for _, f := range files {
260+
if f.IsDir() {
261+
paths = append(paths, f.Name())
262+
}
263+
}
264+
}
265+
266+
for _, path := range paths {
267+
fs := unix.Statfs_t{}
268+
err := unix.Statfs(basepath+"/"+path, &fs)
269+
if err != nil {
270+
log.Print("Failed to stat "+basepath+"/"+path+": ", err)
271+
continue
272+
}
273+
labels := prometheus.Labels{"data_name": path}
274+
275+
dataFree.With(labels).Set(float64(fs.Bfree * uint64(fs.Bsize)))
276+
dataAvailable.With(labels).Set(float64(fs.Bavail * uint64(fs.Bsize)))
277+
dataSize.With(labels).Set(float64(fs.Blocks * uint64(fs.Bsize)))
278+
dataInodesFree.With(labels).Set(float64(fs.Ffree))
279+
dataInodes.With(labels).Set(float64(fs.Files))
280+
}
281+
for id, labels := range knownDataNames {
282+
if newKnownDataNames[id] == nil {
283+
dataFree.Delete(labels)
284+
dataAvailable.Delete(labels)
285+
dataSize.Delete(labels)
286+
dataInodesFree.Delete(labels)
287+
dataInodes.Delete(labels)
288+
}
289+
}
290+
knownDataNames = newKnownDataNames
291+
}
292+
293+
func updateMetrics(docker *client.Client, basepath string) {
294+
for {
295+
updateContainers(docker)
296+
updateDatas(basepath)
297+
}
298+
}
299+
300+
func main() {
301+
basepath := "/"
302+
if len(os.Args) > 1 {
303+
_, err := ioutil.ReadDir(os.Args[1])
304+
if err != nil {
305+
log.Fatal(err)
306+
}
307+
basepath = os.Args[1]
308+
}
309+
310+
docker, err := client.NewClientWithOpts(client.FromEnv)
311+
if err != nil {
312+
panic(err)
313+
}
314+
315+
setup()
316+
go updateMetrics(docker, basepath)
317+
318+
http.Handle("/metrics", promhttp.Handler())
319+
http.ListenAndServe(":8080", nil)
320+
}

0 commit comments

Comments
 (0)