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

Container Uptime in Docker Input Plugin #5996

Merged
merged 7 commits into from
Jun 19, 2019
1 change: 1 addition & 0 deletions plugins/inputs/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ status if configured.
- exitcode (integer)
- started_at (integer)
- finished_at (integer)
- uptime_ns (integer)

- docker_swarm
- tags:
Expand Down
20 changes: 14 additions & 6 deletions plugins/inputs/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
var (
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[bB]?$`)
containerStates = []string{"created", "restarting", "running", "removing", "paused", "exited", "dead"}
now = time.Now
)

var sampleConfig = `
Expand Down Expand Up @@ -462,14 +463,21 @@ func (d *Docker) gatherContainer(
"pid": info.State.Pid,
"exitcode": info.State.ExitCode,
}
container_time, err := time.Parse(time.RFC3339, info.State.StartedAt)
if err == nil && !container_time.IsZero() {
statefields["started_at"] = container_time.UnixNano()

finished, err := time.Parse(time.RFC3339, info.State.FinishedAt)
if err == nil && !finished.IsZero() {
statefields["finished_at"] = finished.UnixNano()
} else {
// set finished to now for use in uptime
finished = now()
}
container_time, err = time.Parse(time.RFC3339, info.State.FinishedAt)
if err == nil && !container_time.IsZero() {
statefields["finished_at"] = container_time.UnixNano()

started, err := time.Parse(time.RFC3339, info.State.StartedAt)
if err == nil && !started.IsZero() {
statefields["started_at"] = started.UnixNano()
statefields["uptime_ns"] = finished.Sub(started).Nanoseconds()
}

acc.AddFields("docker_container_status", statefields, tags, time.Now())

if info.State.Health != nil {
Expand Down
134 changes: 132 additions & 2 deletions plugins/inputs/docker/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strings"
"testing"
"time"

"github.com/influxdata/telegraf/testutil"

Expand Down Expand Up @@ -83,7 +84,7 @@ var baseClient = MockClient{
return containerStats(s), nil
},
ContainerInspectF: func(context.Context, string) (types.ContainerJSON, error) {
return containerInspect, nil
return containerInspect(), nil
},
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
return ServiceList, nil
Expand Down Expand Up @@ -264,7 +265,7 @@ func TestDocker_WindowsMemoryContainerStats(t *testing.T) {
return containerStatsWindows(), nil
},
ContainerInspectF: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
return containerInspect, nil
return containerInspect(), nil
},
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
return ServiceList, nil
Expand Down Expand Up @@ -538,6 +539,135 @@ func TestContainerNames(t *testing.T) {
}
}

func TestContainerStatus(t *testing.T) {
type expectation struct {
// tags
Status string
// fields
OOMKilled bool
Pid int
ExitCode int
StartedAt time.Time
FinishedAt time.Time
UptimeNs int64
}

var tests = []struct {
name string
now func() time.Time
inspect types.ContainerJSON
expect expectation
}{
{
name: "finished_at is zero value",
now: func() time.Time {
return time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC)
},
inspect: containerInspect(),
expect: expectation{
Status: "running",
OOMKilled: false,
Pid: 1234,
ExitCode: 0,
StartedAt: time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC),
UptimeNs: int64(3 * time.Minute),
},
},
{
name: "finished_at is non-zero value",
inspect: func() types.ContainerJSON {
i := containerInspect()
i.ContainerJSONBase.State.FinishedAt = "2018-06-14T05:53:53.266176036Z"
return i
}(),
expect: expectation{
Status: "running",
OOMKilled: false,
Pid: 1234,
ExitCode: 0,
StartedAt: time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC),
FinishedAt: time.Date(2018, 6, 14, 5, 53, 53, 266176036, time.UTC),
UptimeNs: int64(5 * time.Minute),
},
},
{
name: "started_at is zero value",
inspect: func() types.ContainerJSON {
i := containerInspect()
i.ContainerJSONBase.State.StartedAt = ""
i.ContainerJSONBase.State.FinishedAt = "2018-06-14T05:53:53.266176036Z"
return i
}(),
expect: expectation{
Status: "running",
OOMKilled: false,
Pid: 1234,
ExitCode: 0,
FinishedAt: time.Date(2018, 6, 14, 5, 53, 53, 266176036, time.UTC),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
acc testutil.Accumulator
newClientFunc = func(string, *tls.Config) (Client, error) {
client := baseClient
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
return containerList[:1], nil
}
client.ContainerInspectF = func(c context.Context, s string) (types.ContainerJSON, error) {
return tt.inspect, nil
}

return &client, nil
}
d = Docker{newClient: newClientFunc}
)

// mock time
if tt.now != nil {
now = tt.now
}
defer func() {
now = time.Now
}()

err := acc.GatherError(d.Gather)
require.NoError(t, err)

fields := map[string]interface{}{
"oomkilled": tt.expect.OOMKilled,
"pid": tt.expect.Pid,
"exitcode": tt.expect.ExitCode,
}

if started := tt.expect.StartedAt; !started.IsZero() {
fields["started_at"] = started.UnixNano()
fields["uptime_ns"] = tt.expect.UptimeNs
}

if finished := tt.expect.FinishedAt; !finished.IsZero() {
fields["finished_at"] = finished.UnixNano()
}

acc.AssertContainsTaggedFields(t,
"docker_container_status",
fields,
map[string]string{
"container_name": "etcd",
"container_image": "quay.io/coreos/etcd",
"container_version": "v2.2.2",
"engine_host": "absol",
"label1": "test_value_1",
"label2": "test_value_2",
"server_version": "17.09.0-ce",
"container_status": tt.expect.Status,
})
})
}
}

func TestDockerGatherInfo(t *testing.T) {
var acc testutil.Accumulator
d := Docker{
Expand Down
52 changes: 27 additions & 25 deletions plugins/inputs/docker/docker_testdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,32 +492,34 @@ func containerStatsWindows() types.ContainerStats {
return stat
}

var containerInspect = types.ContainerJSON{
Config: &container.Config{
Env: []string{
"ENVVAR1=loremipsum",
"ENVVAR1FOO=loremipsum",
"ENVVAR2=dolorsitamet",
"ENVVAR3==ubuntu:10.04",
"ENVVAR4",
"ENVVAR5=",
"ENVVAR6= ",
"ENVVAR7=ENVVAR8=ENVVAR9",
"PATH=/bin:/sbin",
func containerInspect() types.ContainerJSON {
return types.ContainerJSON{
Config: &container.Config{
Env: []string{
"ENVVAR1=loremipsum",
"ENVVAR1FOO=loremipsum",
"ENVVAR2=dolorsitamet",
"ENVVAR3==ubuntu:10.04",
"ENVVAR4",
"ENVVAR5=",
"ENVVAR6= ",
"ENVVAR7=ENVVAR8=ENVVAR9",
"PATH=/bin:/sbin",
},
},
},
ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{
Health: &types.Health{
FailingStreak: 1,
Status: "Unhealthy",
ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{
Health: &types.Health{
FailingStreak: 1,
Status: "Unhealthy",
},
Status: "running",
OOMKilled: false,
Pid: 1234,
ExitCode: 0,
StartedAt: "2018-06-14T05:48:53.266176036Z",
FinishedAt: "0001-01-01T00:00:00Z",
},
Status: "running",
OOMKilled: false,
Pid: 1234,
ExitCode: 0,
StartedAt: "2018-06-14T05:48:53.266176036Z",
FinishedAt: "0001-01-01T00:00:00Z",
},
},
}
}