Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inputs.docker): Add disk usage #13894

Merged
merged 11 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ require (
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/semver/v3 v3.2.0
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/alecthomas/participle v0.4.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
Expand Down
26 changes: 26 additions & 0 deletions plugins/inputs/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
# container_state_include = []
# container_state_exclude = []

## Objects to include for disk usage query
## Allowed values are "container", "image", "volume"
## When empty disk usage is excluded
storage_objects = []

## Timeout for docker list, info, and stats commands
timeout = "5s"

Expand Down Expand Up @@ -379,6 +384,23 @@ status if configured.
- tasks_desired
- tasks_running

- docker_disk_usage
- tags:
- engine_host
- server_version
- container_name
- container_image
- container_version
- image_id
- image_name
- image_version
- volume_name
- fields:
- size_rw
- size_root_fs
- size
- shared_size

## Example Output

```text
Expand All @@ -392,4 +414,8 @@ docker_container_net,container_image=telegraf,container_name=zen_ritchie,contain
docker_container_blkio,container_image=telegraf,container_name=zen_ritchie,container_status=running,container_version=unknown,device=254:0,engine_host=debian-stretch-docker,server_version=17.09.0-ce container_id="adc4ba9593871bf2ab95f3ffde70d1b638b897bb225d21c2c9c84226a10a8cf4",io_service_bytes_recursive_async=27398144i,io_service_bytes_recursive_read=27398144i,io_service_bytes_recursive_sync=0i,io_service_bytes_recursive_total=27398144i,io_service_bytes_recursive_write=0i,io_serviced_recursive_async=529i,io_serviced_recursive_read=529i,io_serviced_recursive_sync=0i,io_serviced_recursive_total=529i,io_serviced_recursive_write=0i 1524002042000000000
docker_container_health,container_image=telegraf,container_name=zen_ritchie,container_status=running,container_version=unknown,engine_host=debian-stretch-docker,server_version=17.09.0-ce failing_streak=0i,health_status="healthy" 1524007529000000000
docker_swarm,service_id=xaup2o9krw36j2dy1mjx1arjw,service_mode=replicated,service_name=test tasks_desired=3,tasks_running=3 1508968160000000000
docker_disk_usage,engine_host=docker-desktop,server_version=24.0.5 layers_size=17654519107i 1695742041000000000
docker_disk_usage,container_image=influxdb,container_name=frosty_wright,container_version=1.8,engine_host=docker-desktop,server_version=24.0.5 size_root_fs=286593526i,size_rw=538i 1695742041000000000
docker_disk_usage,engine_host=docker-desktop,image_id=7f4a1cc74046,image_name=telegraf,image_version=latest,server_version=24.0.5 shared_size=0i,size=425484494i 1695742041000000000
docker_disk_usage,engine_host=docker-desktop,server_version=24.0.5,volume_name=docker_influxdb-data size=91989940i 1695742041000000000
```
13 changes: 11 additions & 2 deletions plugins/inputs/docker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
)

var (
version = "1.24" // https://docs.docker.com/engine/api/
defaultHeaders = map[string]string{"User-Agent": "engine-api-cli-1.0"}
)

Expand All @@ -23,6 +22,8 @@ type Client interface {
ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error)
ClientVersion() string
Close() error
}

Expand All @@ -43,7 +44,7 @@ func NewClient(host string, tlsConfig *tls.Config) (Client, error) {
client, err := dockerClient.NewClientWithOpts(
dockerClient.WithHTTPHeaders(defaultHeaders),
dockerClient.WithHTTPClient(httpClient),
dockerClient.WithVersion(version),
dockerClient.WithAPIVersionNegotiation(),
dockerClient.WithHost(host))
if err != nil {
return nil, err
Expand Down Expand Up @@ -77,6 +78,14 @@ func (c *SocketClient) TaskList(ctx context.Context, options types.TaskListOptio
func (c *SocketClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
return c.client.NodeList(ctx, options)
}
func (c *SocketClient) DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error) {
return c.client.DiskUsage(ctx, options)
}

func (c *SocketClient) ClientVersion() string {
return c.client.ClientVersion()
}

func (c *SocketClient) Close() error {
return c.client.Close()
}
151 changes: 142 additions & 9 deletions plugins/inputs/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"sync"
"time"

"github.com/Masterminds/semver/v3"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
Expand Down Expand Up @@ -53,6 +54,8 @@ type Docker struct {
ContainerStateInclude []string `toml:"container_state_include"`
ContainerStateExclude []string `toml:"container_state_exclude"`

StorageObjects []string `toml:"storage_objects"`

IncludeSourceTag bool `toml:"source_tag"`

Log telegraf.Logger
Expand All @@ -69,6 +72,7 @@ type Docker struct {
labelFilter filter.Filter
containerFilter filter.Filter
stateFilter filter.Filter
objectTypes []types.DiskUsageObject
}

// KB, MB, GB, TB, PB...human friendly
Expand All @@ -87,6 +91,9 @@ var (
containerStates = []string{"created", "restarting", "running", "removing", "paused", "exited", "dead"}
containerMetricClasses = []string{"cpu", "network", "blkio"}
now = time.Now

minVersion = semver.MustParse("1.23")
minDiskUsageVersion = semver.MustParse("1.42")
)

func (*Docker) SampleConfig() string {
Expand Down Expand Up @@ -123,6 +130,21 @@ func (d *Docker) Init() error {
}
}

d.objectTypes = make([]types.DiskUsageObject, 0, len(d.StorageObjects))

for _, object := range d.StorageObjects {
switch object {
case "container":
d.objectTypes = append(d.objectTypes, types.ContainerObject)
case "image":
d.objectTypes = append(d.objectTypes, types.ImageObject)
case "volume":
d.objectTypes = append(d.objectTypes, types.VolumeObject)
default:
d.Log.Warnf("Unrecognized storage object type: %s", object)
}
}

return nil
}

Expand All @@ -134,6 +156,19 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
return err
}
d.client = c

version, err := semver.NewVersion(d.client.ClientVersion())
if err != nil {
return err
}

if version.LessThan(minVersion) {
d.Log.Warnf("Unsupported api version (%v.%v), upgrade to docker engine 1.12 or later (api version 1.24)",
version.Major(), version.Minor())
} else if version.LessThan(minDiskUsageVersion) && len(d.objectTypes) > 0 {
d.Log.Warnf("Unsupported api version for disk usage (%v.%v), upgrade to docker engine 23.0 or later (api version 1.42)",
version.Major(), version.Minor())
}
}

// Close any idle connections in the end of gathering
Expand Down Expand Up @@ -209,6 +244,11 @@ func (d *Docker) Gather(acc telegraf.Accumulator) error {
}
wg.Wait()

// Get disk usage data
if len(d.objectTypes) > 0 {
d.gatherDiskUsage(acc, types.DiskUsageOptions{Types: d.objectTypes})
}

return nil
}

Expand Down Expand Up @@ -412,21 +452,27 @@ func hostnameFromID(id string) string {
return id
}

func (d *Docker) gatherContainer(
container types.Container,
acc telegraf.Accumulator,
) error {
var v *types.StatsJSON

// Parse container name
// Parse container name
func parseContainerName(containerNames []string) string {
var cname string
for _, name := range container.Names {

for _, name := range containerNames {
trimmedName := strings.TrimPrefix(name, "/")
if !strings.Contains(trimmedName, "/") {
cname = trimmedName
break
return cname
}
}
return cname
}

func (d *Docker) gatherContainer(
container types.Container,
acc telegraf.Accumulator,
) error {
var v *types.StatsJSON

cname := parseContainerName(container.Names)

if cname == "" {
return nil
Expand Down Expand Up @@ -849,6 +895,93 @@ func (d *Docker) gatherBlockIOMetrics(
}
}

func (d *Docker) gatherDiskUsage(acc telegraf.Accumulator, opts types.DiskUsageOptions) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(d.Timeout))
defer cancel()

du, err := d.client.DiskUsage(ctx, opts)

if err != nil {
acc.AddError(err)
}

now := time.Now()
duName := "docker_disk_usage"

// Layers size
fields := map[string]interface{}{
"layers_size": du.LayersSize,
}

tags := map[string]string{
"engine_host": d.engineHost,
"server_version": d.serverVersion,
}

acc.AddFields(duName, fields, tags, now)

// Containers
for _, container := range du.Containers {
fields := map[string]interface{}{
"size_rw": container.SizeRw,
"size_root_fs": container.SizeRootFs,
}

imageName, imageVersion := dockerint.ParseImage(container.Image)

tags := map[string]string{
"engine_host": d.engineHost,
"server_version": d.serverVersion,
"container_name": parseContainerName(container.Names),
"container_image": imageName,
"container_version": imageVersion,
}

if d.IncludeSourceTag {
tags["source"] = hostnameFromID(container.ID)
}

acc.AddFields(duName, fields, tags, now)
}

// Images
for _, image := range du.Images {
fields := map[string]interface{}{
"size": image.Size,
"shared_size": image.SharedSize,
}

tags := map[string]string{
"engine_host": d.engineHost,
"server_version": d.serverVersion,
"image_id": image.ID[7:19], // remove "sha256:" and keep the first 12 characters
}

if len(image.RepoTags) > 0 {
imageName, imageVersion := dockerint.ParseImage(image.RepoTags[0])
tags["image_name"] = imageName
tags["image_version"] = imageVersion
}

acc.AddFields(duName, fields, tags, now)
}

// Volumes
for _, volume := range du.Volumes {
fields := map[string]interface{}{
"size": volume.UsageData.Size,
}

tags := map[string]string{
"engine_host": d.engineHost,
"server_version": d.serverVersion,
"volume_name": volume.Name,
}

acc.AddFields(duName, fields, tags, now)
}
}

func copyTags(in map[string]string) map[string]string {
out := make(map[string]string)
for k, v := range in {
Expand Down
Loading