From fcb230e14df042c50c9d5747e1c0caa239e46047 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 09:26:22 +0100 Subject: [PATCH 1/7] Test docker_container_status metric in docker input plugin --- plugins/inputs/docker/docker_test.go | 102 ++++++++++++++++++++++- plugins/inputs/docker/docker_testdata.go | 52 ++++++------ 2 files changed, 127 insertions(+), 27 deletions(-) diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index 9209c60085519..e2d1cb23758e9 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -3,10 +3,12 @@ package docker import ( "context" "crypto/tls" + "fmt" "io/ioutil" "sort" "strings" "testing" + "time" "github.com/influxdata/telegraf/testutil" @@ -83,7 +85,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 @@ -264,7 +266,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 @@ -538,6 +540,102 @@ 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 + } + + var tests = []struct { + name string + inspect types.ContainerJSON + expect expectation + }{ + { + name: "finished_at is zero value", + inspect: containerInspect(), + expect: expectation{ + Status: "running", + OOMKilled: false, + Pid: 1234, + ExitCode: 0, + StartedAt: time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC), + }, + }, + { + 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), + }, + }, + } + 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} + + 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, + "started_at": tt.expect.StartedAt.UnixNano(), + } + + 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, + }) + + fmt.Printf("%q\n", acc.Metrics) + }) + } +} + func TestDockerGatherInfo(t *testing.T) { var acc testutil.Accumulator d := Docker{ diff --git a/plugins/inputs/docker/docker_testdata.go b/plugins/inputs/docker/docker_testdata.go index 7302e219def01..ba5c2ffa17bdd 100644 --- a/plugins/inputs/docker/docker_testdata.go +++ b/plugins/inputs/docker/docker_testdata.go @@ -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", }, - }, + } } From 6ff4d1b2e7fff8e7fe86891792e05dfa95abb009 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 09:35:43 +0100 Subject: [PATCH 2/7] Add failing test to docker input plugin for uptime_ns field --- plugins/inputs/docker/docker.go | 1 + plugins/inputs/docker/docker_test.go | 42 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index 117aabfb4eeb2..c856fc293c91f 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -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 = ` diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index e2d1cb23758e9..ad515212c5ba0 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -3,7 +3,6 @@ package docker import ( "context" "crypto/tls" - "fmt" "io/ioutil" "sort" "strings" @@ -550,15 +549,20 @@ func TestContainerStatus(t *testing.T) { 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", + 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", @@ -566,6 +570,7 @@ func TestContainerStatus(t *testing.T) { Pid: 1234, ExitCode: 0, StartedAt: time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC), + UptimeNs: int64(3 * time.Minute), }, }, { @@ -582,26 +587,34 @@ func TestContainerStatus(t *testing.T) { 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), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var acc testutil.Accumulator + 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 + } - 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} + ) - return &client, nil + if tt.now != nil { + now = tt.now } - - d := Docker{newClient: newClientFunc} + defer func() { + now = time.Now + }() err := acc.GatherError(d.Gather) require.NoError(t, err) @@ -611,6 +624,7 @@ func TestContainerStatus(t *testing.T) { "pid": tt.expect.Pid, "exitcode": tt.expect.ExitCode, "started_at": tt.expect.StartedAt.UnixNano(), + "uptime_ns": tt.expect.UptimeNs, } if finished := tt.expect.FinishedAt; !finished.IsZero() { @@ -630,8 +644,6 @@ func TestContainerStatus(t *testing.T) { "server_version": "17.09.0-ce", "container_status": tt.expect.Status, }) - - fmt.Printf("%q\n", acc.Metrics) }) } } From e08838617b9c4a105bfd82beabae67e935727164 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 09:58:30 +0100 Subject: [PATCH 3/7] Add uptime_ns to docker input plugin docker_container_status metric --- plugins/inputs/docker/docker.go | 30 ++++++++++++++++++++++------ plugins/inputs/docker/docker_test.go | 1 + 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index c856fc293c91f..23df1a93d9c30 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -463,14 +463,32 @@ 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() + + parseTime := func(v string) (time.Time, bool) { + target, err := time.Parse(time.RFC3339, v) + if err != nil || target.IsZero() { + return time.Time{}, false + } + + return target, true + } + + finished, finishedSet := parseTime(info.State.FinishedAt) + if started, ok := parseTime(info.State.StartedAt); ok { + statefields["started_at"] = started.UnixNano() + + end := now() + if finishedSet { + end = finished + } + + statefields["uptime_ns"] = int64(end.Sub(started)) } - container_time, err = time.Parse(time.RFC3339, info.State.FinishedAt) - if err == nil && !container_time.IsZero() { - statefields["finished_at"] = container_time.UnixNano() + + if finishedSet { + statefields["finished_at"] = finished.UnixNano() } + acc.AddFields("docker_container_status", statefields, tags, time.Now()) if info.State.Health != nil { diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index ad515212c5ba0..f8e7acee02aef 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -609,6 +609,7 @@ func TestContainerStatus(t *testing.T) { d = Docker{newClient: newClientFunc} ) + // mock time if tt.now != nil { now = tt.now } From 367361f96c58ddb84e72f4e984e632a052b40630 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 10:05:54 +0100 Subject: [PATCH 4/7] Update docker_container_status metric documentation in README --- plugins/inputs/docker/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inputs/docker/README.md b/plugins/inputs/docker/README.md index 39fc7d6a64e29..a26b5763e8fe5 100644 --- a/plugins/inputs/docker/README.md +++ b/plugins/inputs/docker/README.md @@ -278,6 +278,7 @@ status if configured. - exitcode (integer) - started_at (integer) - finished_at (integer) + - uptime_ns (integer) - docker_swarm - tags: From 2ea0b086d3aebad70acad7495e9e00c94f0ccc2d Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 12:38:27 +0100 Subject: [PATCH 5/7] Add test case for docker_container_status started_at omitted regression --- plugins/inputs/docker/docker_test.go | 29 +++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/docker/docker_test.go b/plugins/inputs/docker/docker_test.go index f8e7acee02aef..e1a425314d932 100644 --- a/plugins/inputs/docker/docker_test.go +++ b/plugins/inputs/docker/docker_test.go @@ -590,6 +590,22 @@ func TestContainerStatus(t *testing.T) { 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) { @@ -621,11 +637,14 @@ func TestContainerStatus(t *testing.T) { require.NoError(t, err) fields := map[string]interface{}{ - "oomkilled": tt.expect.OOMKilled, - "pid": tt.expect.Pid, - "exitcode": tt.expect.ExitCode, - "started_at": tt.expect.StartedAt.UnixNano(), - "uptime_ns": tt.expect.UptimeNs, + "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() { From 3389359d20c423144b51ea1047004325815becd0 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Sat, 15 Jun 2019 12:44:49 +0100 Subject: [PATCH 6/7] Cleanup uptime_ns field derivation in docker input plugin --- plugins/inputs/docker/docker.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index 23df1a93d9c30..99b2af9127028 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -464,29 +464,18 @@ func (d *Docker) gatherContainer( "exitcode": info.State.ExitCode, } - parseTime := func(v string) (time.Time, bool) { - target, err := time.Parse(time.RFC3339, v) - if err != nil || target.IsZero() { - return time.Time{}, false - } - - return target, true + 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() } - finished, finishedSet := parseTime(info.State.FinishedAt) - if started, ok := parseTime(info.State.StartedAt); ok { + started, err := time.Parse(time.RFC3339, info.State.StartedAt) + if err == nil && !started.IsZero() { statefields["started_at"] = started.UnixNano() - - end := now() - if finishedSet { - end = finished - } - - statefields["uptime_ns"] = int64(end.Sub(started)) - } - - if finishedSet { - statefields["finished_at"] = finished.UnixNano() + statefields["uptime_ns"] = int64(finished.Sub(started)) } acc.AddFields("docker_container_status", statefields, tags, time.Now()) From 1bcbd81a6974f7fda2a8e817da0a726670e7ba64 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Tue, 18 Jun 2019 12:11:11 +0100 Subject: [PATCH 7/7] Use time.Nanoseconds() over int64 cast in docker input plugin --- plugins/inputs/docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/docker/docker.go b/plugins/inputs/docker/docker.go index 99b2af9127028..f9b538080c7c6 100644 --- a/plugins/inputs/docker/docker.go +++ b/plugins/inputs/docker/docker.go @@ -475,7 +475,7 @@ func (d *Docker) gatherContainer( started, err := time.Parse(time.RFC3339, info.State.StartedAt) if err == nil && !started.IsZero() { statefields["started_at"] = started.UnixNano() - statefields["uptime_ns"] = int64(finished.Sub(started)) + statefields["uptime_ns"] = finished.Sub(started).Nanoseconds() } acc.AddFields("docker_container_status", statefields, tags, time.Now())