Beispiel #1
0
func TestMetricCopy(t *testing.T) {
	want := report.MakeMetric()
	have := want.Copy()
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}

	want = report.MakeMetric().Add(time.Now(), 1)
	have = want.Copy()
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}
}
Beispiel #2
0
func (c *container) memoryUsageMetric(stats []docker.Stats) report.Metric {
	result := report.MakeMetric()
	for _, s := range stats {
		result = result.Add(s.Read, float64(s.MemoryStats.Usage)).WithMax(float64(s.MemoryStats.Limit))
	}
	return result
}
Beispiel #3
0
func TestMetricRowSummary(t *testing.T) {
	var (
		now    = time.Now()
		metric = report.MakeMetric().Add(now, 1.234)
		row    = report.MetricRow{
			ID:       "id",
			Format:   "format",
			Group:    "group",
			Value:    1.234,
			Priority: 1,
			Metric:   &metric,
		}
		summary = row.Summary()
	)
	// summary should not have any samples
	if summary.Metric.Len() != 0 {
		t.Errorf("Expected summary to have no samples, but had %d", summary.Metric.Len())
	}
	// original metric should still have its samples
	if metric.Len() != 1 {
		t.Errorf("Expected original metric to still have it's samples, but had %d", metric.Len())
	}
	// summary should have all the same fields (minus the metric)
	summary.Metric = nil
	row.Metric = nil
	if !reflect.DeepEqual(summary, row) {
		t.Errorf("Expected summary to have same fields as original: %s", test.Diff(summary, row))
	}
}
Beispiel #4
0
func TestMetricDiv(t *testing.T) {
	t1 := time.Now()
	t2 := time.Now().Add(1 * time.Minute)

	want := report.MakeMetric().
		Add(t1, -2).
		Add(t2, 2)
	beforeDiv := report.MakeMetric().
		Add(t1, -2048).
		Add(t2, 2048)
	have := beforeDiv.Div(1024)
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}

	// Check the original was unmodified
	checkMetric(t, beforeDiv, t1, t2, -2048, 2048)
}
Beispiel #5
0
func TestMetricMerge(t *testing.T) {
	t1 := time.Now()
	t2 := time.Now().Add(1 * time.Minute)
	t3 := time.Now().Add(2 * time.Minute)
	t4 := time.Now().Add(3 * time.Minute)

	metric1 := report.MakeMetric().
		Add(t2, 0.2).
		Add(t3, 0.31)

	metric2 := report.MakeMetric().
		Add(t1, -0.1).
		Add(t3, 0.3).
		Add(t4, 0.4)

	want := report.MakeMetric().
		Add(t1, -0.1).
		Add(t2, 0.2).
		Add(t3, 0.31).
		Add(t4, 0.4)
	have := metric1.Merge(metric2)
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}

	// Check it didn't modify metric1
	if !metric1.First.Equal(t2) {
		t.Errorf("Expected metric1.First == %q, but was: %q", t2, metric1.First)
	}
	if !metric1.Last.Equal(t3) {
		t.Errorf("Expected metric1.Last == %q, but was: %q", t3, metric1.Last)
	}
	if metric1.Min != 0.0 {
		t.Errorf("Expected metric1.Min == %f, but was: %f", 0.0, metric1.Min)
	}
	if metric1.Max != 0.31 {
		t.Errorf("Expected metric1.Max == %f, but was: %f", 0.31, metric1.Max)
	}

	// Check the result is not the same instance as metric1
	if &metric1 == &have {
		t.Errorf("Expected different pointers for metric1 and have, but both were: %p", &have)
	}
}
Beispiel #6
0
func (r *Reporter) processTopology() (report.Topology, error) {
	t := report.MakeTopology().
		WithMetadataTemplates(MetadataTemplates).
		WithMetricTemplates(MetricTemplates)
	now := mtime.Now()
	deltaTotal, maxCPU, err := r.jiffies()
	if err != nil {
		return t, err
	}

	err = r.walker.Walk(func(p, prev Process) {
		pidstr := strconv.Itoa(p.PID)
		nodeID := report.MakeProcessNodeID(r.scope, pidstr)
		node := report.MakeNode(nodeID)
		for _, tuple := range []struct{ key, value string }{
			{PID, pidstr},
			{Name, p.Name},
			{Cmdline, p.Cmdline},
			{Threads, strconv.Itoa(p.Threads)},
		} {
			if tuple.value != "" {
				node = node.WithLatests(map[string]string{tuple.key: tuple.value})
			}
		}

		if p.PPID > 0 {
			node = node.WithLatests(map[string]string{PPID: strconv.Itoa(p.PPID)})
		}

		if deltaTotal > 0 {
			cpuUsage := float64(p.Jiffies-prev.Jiffies) / float64(deltaTotal) * 100.
			node = node.WithMetric(CPUUsage, report.MakeMetric().Add(now, cpuUsage).WithMax(maxCPU))
		}

		node = node.WithMetric(MemoryUsage, report.MakeMetric().Add(now, float64(p.RSSBytes)).WithMax(float64(p.RSSBytesLimit)))
		node = node.WithMetric(OpenFilesCount, report.MakeMetric().Add(now, float64(p.OpenFilesCount)).WithMax(float64(p.OpenFilesLimit)))

		t.AddNode(node)
	})

	return t, err
}
Beispiel #7
0
func TestMetricsCopy(t *testing.T) {
	t1 := time.Now()
	want := report.Metrics{
		"metric1": report.MakeMetric().Add(t1, 0.1),
	}
	delete(want.Copy(), "metric1") // Modify a copy
	have := want.Copy()            // Check the original wasn't affected
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}
}
Beispiel #8
0
func TestMetricFirstLastMinMax(t *testing.T) {
	metric := report.MakeMetric()
	var zero time.Time
	t1 := time.Now()
	t2 := time.Now().Add(1 * time.Minute)
	t3 := time.Now().Add(2 * time.Minute)
	t4 := time.Now().Add(3 * time.Minute)
	other := report.MakeMetric()
	other.Max = 5
	other.Min = -5
	other.First = t1.Add(-1 * time.Minute)
	other.Last = t4.Add(1 * time.Minute)

	tests := []struct {
		f           func(report.Metric) report.Metric
		first, last time.Time
		min, max    float64
	}{
		{nil, zero, zero, 0, 0},
		{func(m report.Metric) report.Metric { return m.Add(t2, 2) }, t2, t2, 0, 2},
		{func(m report.Metric) report.Metric { return m.Add(t1, 1) }, t1, t2, 0, 2},
		{func(m report.Metric) report.Metric { return m.Add(t3, -1) }, t1, t3, -1, 2},
		{func(m report.Metric) report.Metric { return m.Add(t4, 3) }, t1, t4, -1, 3},
		{func(m report.Metric) report.Metric { return m.Merge(other) }, t1.Add(-1 * time.Minute), t4.Add(1 * time.Minute), -5, 5},
	}
	for _, test := range tests {
		oldFirst, oldLast, oldMin, oldMax := metric.First, metric.Last, metric.Min, metric.Max
		oldMetric := metric
		if test.f != nil {
			metric = test.f(metric)
		}

		// Check it didn't modify the old one
		checkMetric(t, oldMetric, oldFirst, oldLast, oldMin, oldMax)

		// Check the new one is as expected
		checkMetric(t, metric, test.first, test.last, test.min, test.max)
	}
}
Beispiel #9
0
func TestMetricAdd(t *testing.T) {
	s := []report.Sample{
		{time.Now(), 0.1},
		{time.Now().Add(1 * time.Minute), 0.2},
		{time.Now().Add(2 * time.Minute), 0.3},
	}

	have := report.MakeMetric().
		Add(s[0].Timestamp, s[0].Value).
		Add(s[2].Timestamp, s[2].Value). // Keeps sorted
		Add(s[1].Timestamp, s[1].Value).
		Add(s[2].Timestamp, 0.5) // Overwrites duplicate timestamps

	want := report.MakeMetric().
		Add(s[0].Timestamp, s[0].Value).
		Add(s[1].Timestamp, s[1].Value).
		Add(s[2].Timestamp, 0.5)

	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}
}
Beispiel #10
0
func (c *container) cpuPercentMetric(stats []docker.Stats) report.Metric {
	result := report.MakeMetric()
	if len(stats) < 2 {
		return result
	}

	previous := stats[0]
	for _, s := range stats[1:] {
		// Copies from docker/api/client/stats.go#L205
		cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage - previous.CPUStats.CPUUsage.TotalUsage)
		systemDelta := float64(s.CPUStats.SystemCPUUsage - previous.CPUStats.SystemCPUUsage)
		cpuPercent := 0.0
		if systemDelta > 0.0 && cpuDelta > 0.0 {
			cpuPercent = (cpuDelta / systemDelta) * float64(len(s.CPUStats.CPUUsage.PercpuUsage)) * 100.0
		}
		result = result.Add(s.Read, cpuPercent)
		available := float64(len(s.CPUStats.CPUUsage.PercpuUsage)) * 100.0
		if available >= result.Max {
			result.Max = available
		}
		previous = s
	}
	return result
}
Beispiel #11
0
func TestMetricsMerge(t *testing.T) {
	t1 := time.Now()
	t2 := time.Now().Add(1 * time.Minute)
	t3 := time.Now().Add(2 * time.Minute)
	t4 := time.Now().Add(3 * time.Minute)

	metrics1 := report.Metrics{
		"metric1": report.MakeMetric().Add(t1, 0.1).Add(t2, 0.2),
		"metric2": report.MakeMetric().Add(t3, 0.3),
	}
	metrics2 := report.Metrics{
		"metric2": report.MakeMetric().Add(t4, 0.4),
		"metric3": report.MakeMetric().Add(t1, 0.1).Add(t2, 0.2),
	}
	want := report.Metrics{
		"metric1": report.MakeMetric().Add(t1, 0.1).Add(t2, 0.2),
		"metric2": report.MakeMetric().Add(t3, 0.3).Add(t4, 0.4),
		"metric3": report.MakeMetric().Add(t1, 0.1).Add(t2, 0.2),
	}
	have := metrics1.Merge(metrics2)
	if !reflect.DeepEqual(want, have) {
		t.Errorf("diff: %s", test.Diff(want, have))
	}
}
Beispiel #12
0
	ClientContainerImageName   = "image/client"
	ServerContainerImageName   = "image/server"

	KubernetesNamespace = "ping"
	ClientPodID         = "ping/pong-a"
	ServerPodID         = "ping/pong-b"
	ClientPodUID        = "5d4c3b2a1"
	ServerPodUID        = "i9h8g7f6e"
	ClientPodNodeID     = report.MakePodNodeID(ClientPodUID)
	ServerPodNodeID     = report.MakePodNodeID(ServerPodUID)
	ServiceName         = "pongservice"
	ServiceID           = "ping/pongservice"
	ServiceUID          = "service1234"
	ServiceNodeID       = report.MakeServiceNodeID(ServiceUID)

	ClientProcess1CPUMetric    = report.MakeMetric().Add(Now, 0.01).WithFirst(Now.Add(-1 * time.Second))
	ClientProcess1MemoryMetric = report.MakeMetric().Add(Now, 0.02).WithFirst(Now.Add(-2 * time.Second))

	ClientContainerCPUMetric    = report.MakeMetric().Add(Now, 0.03).WithFirst(Now.Add(-3 * time.Second))
	ClientContainerMemoryMetric = report.MakeMetric().Add(Now, 0.04).WithFirst(Now.Add(-4 * time.Second))

	ServerContainerCPUMetric    = report.MakeMetric().Add(Now, 0.05).WithFirst(Now.Add(-5 * time.Second))
	ServerContainerMemoryMetric = report.MakeMetric().Add(Now, 0.06).WithFirst(Now.Add(-6 * time.Second))

	ClientHostCPUMetric    = report.MakeMetric().Add(Now, 0.07).WithFirst(Now.Add(-7 * time.Second))
	ClientHostMemoryMetric = report.MakeMetric().Add(Now, 0.08).WithFirst(Now.Add(-8 * time.Second))
	ClientHostLoad1Metric  = report.MakeMetric().Add(Now, 0.09).WithFirst(Now.Add(-9 * time.Second))

	ServerHostCPUMetric    = report.MakeMetric().Add(Now, 0.12).WithFirst(Now.Add(-12 * time.Second))
	ServerHostMemoryMetric = report.MakeMetric().Add(Now, 0.13).WithFirst(Now.Add(-13 * time.Second))
	ServerHostLoad1Metric  = report.MakeMetric().Add(Now, 0.14).WithFirst(Now.Add(-14 * time.Second))
Beispiel #13
0
var GetLoad = func(now time.Time) report.Metrics {
	out, err := exec.Command("w").CombinedOutput()
	if err != nil {
		return nil
	}
	matches := loadRe.FindAllStringSubmatch(string(out), -1)
	if matches == nil || len(matches) < 1 || len(matches[0]) < 4 {
		return nil
	}

	one, err := strconv.ParseFloat(matches[0][1], 64)
	if err != nil {
		return nil
	}
	return report.Metrics{
		Load1: report.MakeMetric().Add(now, one),
	}
}

// GetUptime returns the uptime of the host.
var GetUptime = func() (time.Duration, error) {
	out, err := exec.Command("w").CombinedOutput()
	if err != nil {
		return 0, err
	}
	matches := uptimeRe.FindAllStringSubmatch(string(out), -1)
	if matches == nil || len(matches) < 1 || len(matches[0]) < 4 {
		return 0, err
	}
	d, err := strconv.Atoi(matches[0][1])
	if err != nil {
Beispiel #14
0
func TestSummaries(t *testing.T) {
	{
		// Just a convenient source of some rendered nodes
		have := detailed.Summaries(fixture.Report, render.ProcessRenderer.Render(fixture.Report, render.FilterNoop))
		// The ids of the processes rendered above
		expectedIDs := []string{
			fixture.ClientProcess1NodeID,
			fixture.ClientProcess2NodeID,
			fixture.ServerProcessNodeID,
			fixture.NonContainerProcessNodeID,
			render.IncomingInternetID,
			render.OutgoingInternetID,
		}
		sort.Strings(expectedIDs)

		// It should summarize each node
		ids := []string{}
		for id := range have {
			ids = append(ids, id)
		}
		sort.Strings(ids)
		if !reflect.DeepEqual(expectedIDs, ids) {
			t.Fatalf("Expected Summaries to have summarized every node in the process renderer: %v, but got %v", expectedIDs, ids)
		}
	}

	// It should summarize nodes' metrics
	{
		t1, t2 := mtime.Now().Add(-1*time.Minute), mtime.Now()
		metric := report.MakeMetric().Add(t1, 1).Add(t2, 2)
		input := fixture.Report.Copy()

		input.Process.Nodes[fixture.ClientProcess1NodeID] = input.Process.Nodes[fixture.ClientProcess1NodeID].WithMetrics(report.Metrics{process.CPUUsage: metric})
		have := detailed.Summaries(input, render.ProcessRenderer.Render(input, render.FilterNoop))

		node, ok := have[fixture.ClientProcess1NodeID]
		if !ok {
			t.Fatalf("Expected output to have the node we added the metric to")
		}

		var row report.MetricRow
		ok = false
		for _, metric := range node.Metrics {
			if metric.ID == process.CPUUsage {
				row = metric
				ok = true
				break
			}
		}
		if !ok {
			t.Fatalf("Expected node to have the metric we added")
		}

		// Our summarized MetricRow
		want := report.MetricRow{
			ID:       process.CPUUsage,
			Label:    "CPU",
			Format:   "percent",
			Value:    2,
			Priority: 1,
			Metric: &report.Metric{
				Samples: nil,
				Min:     metric.Min,
				Max:     metric.Max,
				First:   metric.First,
				Last:    metric.Last,
			},
		}
		if !reflect.DeepEqual(want, row) {
			t.Fatalf("Expected to have summarized the node's metrics: %s", test.Diff(want, row))
		}
	}
}
Beispiel #15
0
func TestMetricMarshalling(t *testing.T) {
	t1 := time.Now().UTC()
	t2 := time.Now().UTC().Add(1 * time.Minute)
	t3 := time.Now().UTC().Add(2 * time.Minute)
	t4 := time.Now().UTC().Add(3 * time.Minute)

	wantSamples := []report.Sample{
		{Timestamp: t1, Value: 0.1},
		{Timestamp: t2, Value: 0.2},
		{Timestamp: t3, Value: 0.3},
		{Timestamp: t4, Value: 0.4},
	}

	want := report.MakeMetric()
	for _, sample := range wantSamples {
		want = want.Add(sample.Timestamp, sample.Value)
	}

	// gob
	{
		gobs, err := want.GobEncode()
		if err != nil {
			t.Fatal(err)
		}
		var have report.Metric
		have.GobDecode(gobs)
		if !reflect.DeepEqual(want, have) {
			t.Error(test.Diff(want, have))
		}
	}

	// others
	{

		for _, h := range []codec.Handle{
			codec.Handle(&codec.MsgpackHandle{}),
			codec.Handle(&codec.JsonHandle{}),
		} {
			buf := &bytes.Buffer{}
			encoder := codec.NewEncoder(buf, h)
			want.CodecEncodeSelf(encoder)
			bufCopy := bytes.NewBuffer(buf.Bytes())

			decoder := codec.NewDecoder(buf, h)
			var have report.Metric
			have.CodecDecodeSelf(decoder)

			if !reflect.DeepEqual(want, have) {
				t.Error(test.Diff(want, have))
			}

			// extra check for samples
			decoder = codec.NewDecoder(bufCopy, h)
			var wire struct {
				Samples []report.Sample `json:"samples"`
			}
			if err := decoder.Decode(&wire); err != nil {
				t.Error(err)
			}
			if !reflect.DeepEqual(wantSamples, wire.Samples) {
				t.Error(test.Diff(wantSamples, wire.Samples))
			}
		}
	}

}
Beispiel #16
0
func TestContainer(t *testing.T) {
	log.SetOutput(ioutil.Discard)

	oldDialStub, oldNewClientConnStub := docker.DialStub, docker.NewClientConnStub
	defer func() { docker.DialStub, docker.NewClientConnStub = oldDialStub, oldNewClientConnStub }()

	docker.DialStub = func(network, address string) (net.Conn, error) {
		return nil, nil
	}

	reader, writer := io.Pipe()
	connection := &mockConnection{reader}

	docker.NewClientConnStub = func(c net.Conn, r *bufio.Reader) docker.ClientConn {
		return connection
	}

	now := time.Unix(12345, 67890).UTC()
	mtime.NowForce(now)
	defer mtime.NowReset()

	const hostID = "scope"
	c := docker.NewContainer(container1, hostID)
	err := c.StartGatheringStats()
	if err != nil {
		t.Errorf("%v", err)
	}
	defer c.StopGatheringStats()

	// Send some stats to the docker container
	stats := &client.Stats{}
	stats.Read = now
	stats.MemoryStats.Usage = 12345
	stats.MemoryStats.Limit = 45678
	encoder := codec.NewEncoder(writer, &codec.JsonHandle{})
	if err = encoder.Encode(&stats); err != nil {
		t.Error(err)
	}

	// Now see if we go them
	{
		uptime := (now.Sub(startTime) / time.Second) * time.Second
		want := report.MakeNodeWith("ping;<container>", map[string]string{
			"docker_container_command":     " ",
			"docker_container_created":     "01 Jan 01 00:00 UTC",
			"docker_container_id":          "ping",
			"docker_container_name":        "pong",
			"docker_image_id":              "baz",
			"docker_label_foo1":            "bar1",
			"docker_label_foo2":            "bar2",
			"docker_container_state":       "running",
			"docker_container_state_human": "Up 6 years",
			"docker_container_uptime":      uptime.String(),
		}).
			WithControls(
				docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
				docker.AttachContainer, docker.ExecContainer,
			).WithMetrics(report.Metrics{
			"docker_cpu_total_usage": report.MakeMetric(),
			"docker_memory_usage":    report.MakeMetric().Add(now, 12345).WithMax(45678),
		}).WithParents(report.EmptySets.
			Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
		)

		test.Poll(t, 100*time.Millisecond, want, func() interface{} {
			node := c.GetNode()
			node.Latest.ForEach(func(k, v string) {
				if v == "0" || v == "" {
					node.Latest = node.Latest.Delete(k)
				}
			})
			return node
		})
	}

	{
		want := report.EmptySets.
			Add("docker_container_ports", report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp")).
			Add("docker_container_ips", report.MakeStringSet("1.2.3.4")).
			Add("docker_container_ips_with_scopes", report.MakeStringSet(";1.2.3.4"))

		test.Poll(t, 100*time.Millisecond, want, func() interface{} {
			return c.NetworkInfo([]net.IP{})
		})
	}

	if c.Image() != "baz" {
		t.Errorf("%s != baz", c.Image())
	}
	if c.PID() != 2 {
		t.Errorf("%d != 2", c.PID())
	}
	node := c.GetNode().WithSets(c.NetworkInfo([]net.IP{}))
	if have := docker.ExtractContainerIPs(node); !reflect.DeepEqual(have, []string{"1.2.3.4"}) {
		t.Errorf("%v != %v", have, []string{"1.2.3.4"})
	}
}
Beispiel #17
0
func TestReporter(t *testing.T) {
	var (
		release   = "release"
		version   = "version"
		network   = "192.168.0.0/16"
		hostID    = "hostid"
		hostname  = "hostname"
		timestamp = time.Now()
		metrics   = report.Metrics{
			host.Load1:       report.MakeMetric().Add(timestamp, 1.0),
			host.CPUUsage:    report.MakeMetric().Add(timestamp, 30.0).WithMax(100.0),
			host.MemoryUsage: report.MakeMetric().Add(timestamp, 60.0).WithMax(100.0),
		}
		uptime      = "278h55m43s"
		kernel      = "release version"
		_, ipnet, _ = net.ParseCIDR(network)
	)

	mtime.NowForce(timestamp)
	defer mtime.NowReset()

	var (
		oldGetKernelVersion    = host.GetKernelVersion
		oldGetLoad             = host.GetLoad
		oldGetUptime           = host.GetUptime
		oldGetCPUUsagePercent  = host.GetCPUUsagePercent
		oldGetMemoryUsageBytes = host.GetMemoryUsageBytes
		oldGetLocalNetworks    = host.GetLocalNetworks
	)
	defer func() {
		host.GetKernelVersion = oldGetKernelVersion
		host.GetLoad = oldGetLoad
		host.GetUptime = oldGetUptime
		host.GetCPUUsagePercent = oldGetCPUUsagePercent
		host.GetMemoryUsageBytes = oldGetMemoryUsageBytes
		host.GetLocalNetworks = oldGetLocalNetworks
	}()
	host.GetKernelVersion = func() (string, error) { return release + " " + version, nil }
	host.GetLoad = func(time.Time) report.Metrics { return metrics }
	host.GetUptime = func() (time.Duration, error) { return time.ParseDuration(uptime) }
	host.GetCPUUsagePercent = func() (float64, float64) { return 30.0, 100.0 }
	host.GetMemoryUsageBytes = func() (float64, float64) { return 60.0, 100.0 }
	host.GetLocalNetworks = func() ([]*net.IPNet, error) { return []*net.IPNet{ipnet}, nil }

	rpt, err := host.NewReporter(hostID, hostname, "", "", nil).Report()
	if err != nil {
		t.Fatal(err)
	}

	nodeID := report.MakeHostNodeID(hostID)
	node, ok := rpt.Host.Nodes[nodeID]
	if !ok {
		t.Errorf("Expected host node %q, but not found", nodeID)
	}

	// Should have a bunch of expected latest keys
	for _, tuple := range []struct {
		key, want string
	}{
		{host.Timestamp, timestamp.UTC().Format(time.RFC3339Nano)},
		{host.HostName, hostname},
		{host.OS, runtime.GOOS},
		{host.Uptime, uptime},
		{host.KernelVersion, kernel},
	} {
		if have, ok := node.Latest.Lookup(tuple.key); !ok || have != tuple.want {
			t.Errorf("Expected %s %q, got %q", tuple.key, tuple.want, have)
		}
	}

	// Should have the local network
	if have, ok := node.Sets.Lookup(host.LocalNetworks); !ok || !have.Contains(network) {
		t.Errorf("Expected host.LocalNetworks to include %q, got %q", network, have)
	}

	// Should have metrics
	for key, want := range metrics {
		wantSample := want.LastSample()
		if metric, ok := node.Metrics[key]; !ok {
			t.Errorf("Expected %s metric, but not found", key)
		} else if sample := metric.LastSample(); sample == nil {
			t.Errorf("Expected %s metric to have a sample, but there were none", key)
		} else if sample.Value != wantSample.Value {
			t.Errorf("Expected %s metric sample %f, got %f", key, wantSample, sample.Value)
		}
	}
}