Skip to content

Commit a375a54

Browse files
authored
Merge pull request #3714 from weaveworks/simplify-control-serialisation
performance: send active controls as a single string per node
2 parents 35451b4 + 1dcdfab commit a375a54

13 files changed

+275
-475
lines changed

probe/awsecs/reporter.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,18 @@ func (r Reporter) Tag(rpt report.Report) (report.Report, error) {
150150
// Create all the services first
151151
for serviceName, service := range ecsInfo.Services {
152152
serviceID := report.MakeECSServiceNodeID(cluster, serviceName)
153+
activeControls := []string{ScaleUp}
154+
// Disable ScaleDown when only 1 task is desired, since
155+
// scaling down to 0 would cause the service to disappear (#2085)
156+
if service.DesiredCount > 1 {
157+
activeControls = append(activeControls, ScaleDown)
158+
}
153159
rpt.ECSService.AddNode(report.MakeNodeWith(serviceID, map[string]string{
154160
Cluster: cluster,
155161
ServiceDesiredCount: fmt.Sprintf("%d", service.DesiredCount),
156162
ServiceRunningCount: fmt.Sprintf("%d", service.RunningCount),
157163
report.ControlProbeID: r.probeID,
158-
}).WithLatestControls(map[string]report.NodeControlData{
159-
ScaleUp: {Dead: false},
160-
// We've decided for now to disable ScaleDown when only 1 task is desired,
161-
// since scaling down to 0 would cause the service to disappear (#2085)
162-
ScaleDown: {Dead: service.DesiredCount <= 1},
163-
}))
164+
}).WithLatestActiveControls(activeControls...))
164165
}
165166
log.Debugf("Created %v ECS service nodes", len(ecsInfo.Services))
166167

probe/docker/container.go

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -389,20 +389,17 @@ func (c *container) getBaseNode() report.Node {
389389
return result
390390
}
391391

392-
func (c *container) controlsMap() map[string]report.NodeControlData {
393-
paused := c.container.State.Paused
394-
running := !paused && c.container.State.Running
395-
stopped := !paused && !running
396-
return map[string]report.NodeControlData{
397-
UnpauseContainer: {Dead: !paused},
398-
RestartContainer: {Dead: !running},
399-
StopContainer: {Dead: !running},
400-
PauseContainer: {Dead: !running},
401-
AttachContainer: {Dead: !running},
402-
ExecContainer: {Dead: !running},
403-
StartContainer: {Dead: !stopped},
404-
RemoveContainer: {Dead: !stopped},
392+
// Return a slice including all controls that should be shown on this container
393+
func (c *container) controls() []string {
394+
switch {
395+
case c.container.State.Paused:
396+
return []string{UnpauseContainer}
397+
case c.container.State.Running:
398+
return []string{RestartContainer, StopContainer, PauseContainer, AttachContainer, ExecContainer}
399+
default:
400+
return []string{StartContainer, RemoveContainer}
405401
}
402+
return nil
406403
}
407404

408405
func (c *container) GetNode() report.Node {
@@ -413,7 +410,6 @@ func (c *container) GetNode() report.Node {
413410
ContainerState: c.StateString(),
414411
ContainerStateHuman: c.State(),
415412
}
416-
controls := c.controlsMap()
417413

418414
if !c.container.State.Paused && c.container.State.Running {
419415
uptimeSeconds := int(mtime.Now().Sub(c.container.State.StartedAt) / time.Second)
@@ -427,7 +423,7 @@ func (c *container) GetNode() report.Node {
427423
}
428424

429425
result := c.baseNode.WithLatests(latest)
430-
result = result.WithLatestControls(controls)
426+
result = result.WithLatestActiveControls(c.controls()...)
431427
result = result.WithMetrics(c.metrics())
432428
return result
433429
}

probe/docker/container_test.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,12 @@ func TestContainer(t *testing.T) {
6060
// Now see if we go them
6161
{
6262
uptimeSeconds := int(now.Sub(startTime) / time.Second)
63-
controls := map[string]report.NodeControlData{
64-
docker.UnpauseContainer: {Dead: true},
65-
docker.RestartContainer: {Dead: false},
66-
docker.StopContainer: {Dead: false},
67-
docker.PauseContainer: {Dead: false},
68-
docker.AttachContainer: {Dead: false},
69-
docker.ExecContainer: {Dead: false},
70-
docker.StartContainer: {Dead: true},
71-
docker.RemoveContainer: {Dead: true},
63+
controls := []string{
64+
docker.RestartContainer,
65+
docker.StopContainer,
66+
docker.PauseContainer,
67+
docker.AttachContainer,
68+
docker.ExecContainer,
7269
}
7370
want := report.MakeNodeWith("ping;<container>", map[string]string{
7471
"docker_container_command": "ping foo.bar.local",
@@ -82,8 +79,8 @@ func TestContainer(t *testing.T) {
8279
"docker_container_state_human": c.Container().State.String(),
8380
"docker_container_uptime": strconv.Itoa(uptimeSeconds),
8481
"docker_env_FOO": "secret-bar",
85-
}).WithLatestControls(
86-
controls,
82+
}).WithLatestActiveControls(
83+
controls...,
8784
).WithMetrics(report.Metrics{
8885
"docker_cpu_total_usage": report.MakeMetric(nil),
8986
"docker_memory_usage": report.MakeSingletonMetric(now, 12345).WithMax(45678),

probe/plugins/registry.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ func (r *Registry) updateAndGetControlsInTopology(pluginID string, topology *rep
252252
for name, node := range topology.Nodes {
253253
log.Debugf("plugins: checking node controls in node %s of %s", name, topology.Label)
254254
newNode := node.WithID(name)
255-
newLatestControls := report.MakeNodeControlDataLatestMap()
256-
node.LatestControls.ForEach(func(controlID string, ts time.Time, data report.NodeControlData) {
255+
newLatestControls := []string{}
256+
for _, controlID := range node.ActiveControls() {
257257
log.Debugf("plugins: got node control %s", controlID)
258258
newControlID := ""
259259
if _, found := topology.Controls[controlID]; !found {
@@ -263,9 +263,9 @@ func (r *Registry) updateAndGetControlsInTopology(pluginID string, topology *rep
263263
newControlID = fakeControlID(pluginID, controlID)
264264
log.Debugf("plugins: will replace node control %s with %s", controlID, newControlID)
265265
}
266-
newLatestControls = newLatestControls.Set(newControlID, ts, data)
267-
})
268-
newNode.LatestControls = newLatestControls
266+
newLatestControls = append(newLatestControls, newControlID)
267+
}
268+
newNode = newNode.WithLatestActiveControls(newLatestControls...)
269269
newNodes[newNode.ID] = newNode
270270
}
271271
topology.Controls = newControls

probe/plugins/registry_internal_test.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -627,14 +627,9 @@ func checkControls(t *testing.T, topology report.Topology, expectedControls, exp
627627
if !found {
628628
t.Fatalf("expected a node %s in a topology", nodeID)
629629
}
630-
actualNodeControls := []string{}
631-
node.LatestControls.ForEach(func(controlID string, _ time.Time, _ report.NodeControlData) {
632-
actualNodeControls = append(actualNodeControls, controlID)
633-
})
634-
nodeControlsSet := report.MakeStringSet(expectedNodeControls...)
635-
actualNodeControlsSet := report.MakeStringSet(actualNodeControls...)
636-
if !reflect.DeepEqual(nodeControlsSet, actualNodeControlsSet) {
637-
t.Fatalf("node controls in node %s in topology %s are not equal:\n%s", nodeID, topology.Label, test.Diff(nodeControlsSet, actualNodeControlsSet))
630+
actualNodeControls := node.ActiveControls()
631+
if !reflect.DeepEqual(expectedNodeControls, actualNodeControls) {
632+
t.Fatalf("node controls in node %s in topology %s are not equal:\n%s", nodeID, topology.Label, test.Diff(expectedNodeControls, actualNodeControls))
638633
}
639634
}
640635

render/detailed/node.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package detailed
22

33
import (
44
"sort"
5-
"time"
65

76
"github.com/ugorji/go/codec"
87

@@ -112,18 +111,15 @@ func controlsFor(topology report.Topology, nodeID string) []ControlInstance {
112111
if !ok {
113112
return result
114113
}
115-
node.LatestControls.ForEach(func(controlID string, _ time.Time, data report.NodeControlData) {
116-
if data.Dead {
117-
return
118-
}
114+
for _, controlID := range node.ActiveControls() {
119115
if control, ok := topology.Controls[controlID]; ok {
120116
result = append(result, ControlInstance{
121117
ProbeID: probeID,
122118
NodeID: nodeID,
123119
Control: control,
124120
})
125121
}
126-
})
122+
}
127123
return result
128124
}
129125

report/backcompat.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package report
2+
3+
// Backwards-compatibility: code to read older reports and convert
4+
5+
import (
6+
"strings"
7+
"time"
8+
9+
"github.com/ugorji/go/codec"
10+
)
11+
12+
// For backwards-compatibility with probes that sent a map of latestControls data
13+
type bcNode struct {
14+
Node
15+
LatestControls map[string]nodeControlDataLatestEntry `json:"latestControls,omitempty"`
16+
}
17+
18+
type nodeControlDataLatestEntry struct {
19+
Timestamp time.Time `json:"timestamp"`
20+
Value nodeControlData `json:"value"`
21+
}
22+
23+
type nodeControlData struct {
24+
Dead bool `json:"dead"`
25+
}
26+
27+
// CodecDecodeSelf implements codec.Selfer
28+
func (n *Node) CodecDecodeSelf(decoder *codec.Decoder) {
29+
var in bcNode
30+
decoder.Decode(&in)
31+
*n = in.Node
32+
if len(in.LatestControls) > 0 {
33+
// Convert the map into a delimited string
34+
cs := make([]string, 0, len(in.LatestControls))
35+
var ts time.Time
36+
for name, v := range in.LatestControls {
37+
if !v.Value.Dead {
38+
cs = append(cs, name)
39+
// Pull out the newest timestamp to use for the whole set
40+
if ts.Before(v.Timestamp) {
41+
ts = v.Timestamp
42+
}
43+
}
44+
}
45+
n.Latest = n.Latest.Set(NodeActiveControls, ts, strings.Join(cs, ScopeDelim))
46+
}
47+
}
48+
49+
type _Node Node // just so we don't recurse inside CodecEncodeSelf
50+
51+
// CodecEncodeSelf implements codec.Selfer
52+
func (n *Node) CodecEncodeSelf(encoder *codec.Encoder) {
53+
encoder.Encode((*_Node)(n))
54+
}
55+
56+
// Upgrade returns a new report based on a report received from the old probe.
57+
//
58+
func (r Report) Upgrade() Report {
59+
return r.upgradePodNodes().upgradeNamespaces().upgradeDNSRecords()
60+
}
61+
62+
func (r Report) upgradePodNodes() Report {
63+
// At the same time the probe stopped reporting replicasets,
64+
// it also started reporting deployments as pods' parents
65+
if len(r.ReplicaSet.Nodes) == 0 {
66+
return r
67+
}
68+
69+
// For each pod, we check for any replica sets, and merge any deployments they point to
70+
// into a replacement Parents value.
71+
nodes := Nodes{}
72+
for podID, pod := range r.Pod.Nodes {
73+
if replicaSetIDs, ok := pod.Parents.Lookup(ReplicaSet); ok {
74+
newParents := pod.Parents.Delete(ReplicaSet)
75+
for _, replicaSetID := range replicaSetIDs {
76+
if replicaSet, ok := r.ReplicaSet.Nodes[replicaSetID]; ok {
77+
if deploymentIDs, ok := replicaSet.Parents.Lookup(Deployment); ok {
78+
newParents = newParents.Add(Deployment, deploymentIDs)
79+
}
80+
}
81+
}
82+
// newParents contains a copy of the current parents without replicasets,
83+
// PruneParents().WithParents() ensures replicasets are actually deleted
84+
pod = pod.PruneParents().WithParents(newParents)
85+
}
86+
nodes[podID] = pod
87+
}
88+
r.Pod.Nodes = nodes
89+
90+
return r
91+
}
92+
93+
func (r Report) upgradeNamespaces() Report {
94+
if len(r.Namespace.Nodes) > 0 {
95+
return r
96+
}
97+
98+
namespaces := map[string]struct{}{}
99+
for _, t := range []Topology{r.Pod, r.Service, r.Deployment, r.DaemonSet, r.StatefulSet, r.CronJob} {
100+
for _, n := range t.Nodes {
101+
if state, ok := n.Latest.Lookup(KubernetesState); ok && state == "deleted" {
102+
continue
103+
}
104+
if namespace, ok := n.Latest.Lookup(KubernetesNamespace); ok {
105+
namespaces[namespace] = struct{}{}
106+
}
107+
}
108+
}
109+
110+
nodes := make(Nodes, len(namespaces))
111+
for ns := range namespaces {
112+
// Namespace ID:
113+
// Probes did not use to report namespace ids, but since creating a report node requires an id,
114+
// the namespace name, which is unique, is passed to `MakeNamespaceNodeID`
115+
namespaceID := MakeNamespaceNodeID(ns)
116+
nodes[namespaceID] = MakeNodeWith(namespaceID, map[string]string{KubernetesName: ns})
117+
}
118+
r.Namespace.Nodes = nodes
119+
120+
return r
121+
}
122+
123+
func (r Report) upgradeDNSRecords() Report {
124+
// For release 1.11.6, probes accidentally sent DNS records labeled "nodes".
125+
// Translate the incorrect version here. Accident was in commit 951629a.
126+
if len(r.BugDNS) > 0 {
127+
r.DNS = r.BugDNS
128+
r.BugDNS = nil
129+
}
130+
if len(r.DNS) > 0 {
131+
return r
132+
}
133+
dns := make(DNSRecords)
134+
for endpointID, endpoint := range r.Endpoint.Nodes {
135+
_, addr, _, ok := ParseEndpointNodeID(endpointID)
136+
snoopedNames, foundS := endpoint.Sets.Lookup(SnoopedDNSNames)
137+
reverseNames, foundR := endpoint.Sets.Lookup(ReverseDNSNames)
138+
if ok && (foundS || foundR) {
139+
// Add address and names to report-level map
140+
if existing, found := dns[addr]; found {
141+
var sUnchanged, rUnchanged bool
142+
snoopedNames, sUnchanged = snoopedNames.Merge(existing.Forward)
143+
reverseNames, rUnchanged = reverseNames.Merge(existing.Reverse)
144+
if sUnchanged && rUnchanged {
145+
continue
146+
}
147+
}
148+
dns[addr] = DNSRecord{Forward: snoopedNames, Reverse: reverseNames}
149+
}
150+
}
151+
r.DNS = dns
152+
return r
153+
}

report/controls.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,3 @@ func (cs Controls) AddControls(controls []Control) {
5050
cs[c.ID] = c
5151
}
5252
}
53-
54-
// NodeControlData contains specific information about the control. It
55-
// is used as a Value field of LatestEntry in NodeControlDataLatestMap.
56-
type NodeControlData struct {
57-
Dead bool `json:"dead"`
58-
}

0 commit comments

Comments
 (0)