diff --git a/common/mtime/mtime.go b/common/mtime/mtime.go new file mode 100644 index 000000000..dd1fc87ca --- /dev/null +++ b/common/mtime/mtime.go @@ -0,0 +1,16 @@ +package mtime + +import "time" + +// Now returns the current time. +var Now = func() time.Time { return time.Now() } + +// NowForce sets the time returned by Now to t. +func NowForce(t time.Time) { + Now = func() time.Time { return t } +} + +// NowReset makes Now returns the current time again. +func NowReset() { + Now = func() time.Time { return time.Now() } +} diff --git a/probe/docker/container.go b/probe/docker/container.go index 27e581c2b..82c9bcdc9 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -17,6 +17,7 @@ import ( docker "github.com/fsouza/go-dockerclient" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/report" ) @@ -268,7 +269,7 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node { ContainerPorts: c.ports(localAddrs), ContainerIPs: report.MakeStringSet(ips...), ContainerIPsWithScopes: report.MakeStringSet(ipsWithScopes...), - }).WithLatest(ContainerState, state) + }).WithLatest(ContainerState, mtime.Now(), state) if c.container.State.Paused { result = result.WithControls(UnpauseContainer) diff --git a/probe/docker/container_test.go b/probe/docker/container_test.go index a0dc73deb..a7c60cdd0 100644 --- a/probe/docker/container_test.go +++ b/probe/docker/container_test.go @@ -14,6 +14,7 @@ import ( client "github.com/fsouza/go-dockerclient" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" @@ -64,6 +65,10 @@ func TestContainer(t *testing.T) { t.Error(err) } + now := time.Now() + mtime.NowForce(now) + defer mtime.NowReset() + // Now see if we go them want := report.MakeNode().WithMetadata(map[string]string{ "docker_container_command": " ", @@ -80,7 +85,7 @@ func TestContainer(t *testing.T) { "docker_container_ips_with_scopes": report.MakeStringSet("scope;1.2.3.4"), }).WithControls( docker.RestartContainer, docker.StopContainer, docker.PauseContainer, - ).WithLatest("docker_container_state", "running") + ).WithLatest("docker_container_state", now, "running") test.Poll(t, 100*time.Millisecond, want, func() interface{} { node := c.GetNode("scope", []net.IP{}) diff --git a/render/renderable_node.go b/render/renderable_node.go index eccda8560..8519f3687 100644 --- a/render/renderable_node.go +++ b/render/renderable_node.go @@ -134,12 +134,7 @@ func (rn RenderableNode) Copy() RenderableNode { // Specifically, that means cutting out parts of the Node. func (rn RenderableNode) Prune() RenderableNode { cp := rn.Copy() - cp.Node.Metadata = report.Metadata{} // snip - cp.Node.Counters = report.Counters{} // snip - cp.Node.Edges = report.EdgeMetadatas{} // snip - cp.Node.Sets = report.Sets{} // snip - cp.Node.Controls = report.NodeControls{} // snip - cp.Node.Latest = report.LatestMap{} // snip + cp.Node = report.MakeNode().WithAdjacent(cp.Node.Adjacency...) return cp } diff --git a/report/controls.go b/report/controls.go index be3e7b92e..bb058939a 100644 --- a/report/controls.go +++ b/report/controls.go @@ -2,6 +2,8 @@ package report import ( "time" + + "github.com/weaveworks/scope/common/mtime" ) // Controls describe the control tags within the Nodes @@ -37,18 +39,18 @@ func (cs Controls) AddControl(c Control) { cs[c.ID] = c } -// NodeControls represent the individual controls that are valid -// for a given node at a given point in time. Its is immutable. +// NodeControls represent the individual controls that are valid for a given +// node at a given point in time. Its is immutable. A zero-value for Timestamp +// indicated this NodeControls is 'not set'. type NodeControls struct { - Timestamp int64 `json:"timestamp"` - Controls IDList `json:"controls"` + Timestamp time.Time `json:"timestamp"` + Controls StringSet `json:"controls,omitempty"` } // MakeNodeControls makes a new NodeControls func MakeNodeControls() NodeControls { return NodeControls{ - Timestamp: time.Now().Unix(), - Controls: MakeIDList(), + Controls: MakeStringSet(), } } @@ -60,7 +62,7 @@ func (nc NodeControls) Copy() NodeControls { // Merge returns the newest of the two NodeControls; it does not take the union // of the valid Controls. func (nc NodeControls) Merge(other NodeControls) NodeControls { - if other.Timestamp > nc.Timestamp { + if nc.Timestamp.Before(other.Timestamp) { return other } return nc @@ -69,7 +71,7 @@ func (nc NodeControls) Merge(other NodeControls) NodeControls { // Add the new control IDs to this NodeControls, producing a fresh NodeControls. func (nc NodeControls) Add(ids ...string) NodeControls { return NodeControls{ - Timestamp: time.Now().Unix(), + Timestamp: mtime.Now(), Controls: nc.Controls.Add(ids...), } } diff --git a/report/latest_map.go b/report/latest_map.go index a93c90660..97748db8c 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -18,8 +18,8 @@ type LatestMap struct { // LatestEntry represents a timestamped value inside the LatestMap. type LatestEntry struct { - Timestamp int64 `json:"timestamp"` - Value string `json:"value"` + Timestamp time.Time `json:"timestamp"` + Value string `json:"value"` } func (e LatestEntry) String() string { @@ -46,7 +46,7 @@ func (m LatestMap) Merge(newer LatestMap) LatestMap { m.Map.ForEach(func(key string, olderVal interface{}) { if newerVal, ok := newer.Map.Lookup(key); ok { - if olderVal.(LatestEntry).Timestamp > newerVal.(LatestEntry).Timestamp { + if newerVal.(LatestEntry).Timestamp.Before(olderVal.(LatestEntry).Timestamp) { output = output.Set(key, olderVal) } } else { @@ -67,9 +67,8 @@ func (m LatestMap) Lookup(key string) (string, bool) { } // Set the value for the given key. -func (m LatestMap) Set(key string, value string) LatestMap { - now := time.Now() - return LatestMap{m.Map.Set(key, LatestEntry{now.Unix(), value})} +func (m LatestMap) Set(key string, timestamp time.Time, value string) LatestMap { + return LatestMap{m.Map.Set(key, LatestEntry{timestamp, value})} } func (m LatestMap) toIntermediate() map[string]LatestEntry { diff --git a/report/topology.go b/report/topology.go index 613683a67..9fa6de15c 100644 --- a/report/topology.go +++ b/report/topology.go @@ -4,6 +4,7 @@ import ( "fmt" "sort" "strings" + "time" ) // Topology describes a specific view of a network. It consists of nodes and @@ -140,9 +141,9 @@ func (n Node) WithSets(sets Sets) Node { } // WithAdjacent returns a fresh copy of n, with 'a' added to Adjacency -func (n Node) WithAdjacent(a string) Node { +func (n Node) WithAdjacent(a ...string) Node { result := n.Copy() - result.Adjacency = result.Adjacency.Add(a) + result.Adjacency = result.Adjacency.Add(a...) return result } @@ -163,9 +164,9 @@ func (n Node) WithControls(cs ...string) Node { } // WithLatest produces a new Node with k mapped to v in the Latest metadata. -func (n Node) WithLatest(k, v string) Node { +func (n Node) WithLatest(k string, ts time.Time, v string) Node { result := n.Copy() - result.Latest = result.Latest.Set(k, v) + result.Latest = result.Latest.Set(k, ts, v) return result } diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 7bb8f4b92..e0567760e 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -15,6 +15,8 @@ import ( // This is an example Report: // 2 hosts with probes installed - client & server. var ( + Now = time.Now() + ClientHostID = "client.hostname.com" ServerHostID = "server.hostname.com" UnknownHostID = "" @@ -219,7 +221,7 @@ var ( docker.ImageID: ClientContainerImageID, report.HostNodeID: ClientHostNodeID, docker.LabelPrefix + "io.kubernetes.pod.name": ClientPodID, - }).WithLatest(docker.ContainerState, docker.StateRunning), + }).WithLatest(docker.ContainerState, Now, docker.StateRunning), ServerContainerNodeID: report.MakeNodeWith(map[string]string{ docker.ContainerID: ServerContainerID, docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01", @@ -229,7 +231,7 @@ var ( docker.LabelPrefix + "foo1": "bar1", docker.LabelPrefix + "foo2": "bar2", docker.LabelPrefix + "io.kubernetes.pod.name": ServerPodID, - }).WithLatest(docker.ContainerState, docker.StateRunning), + }).WithLatest(docker.ContainerState, Now, docker.StateRunning), }, }, ContainerImage: report.Topology{