|
| 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