Example #1
0
func TestTagger(t *testing.T) {
	oldProcessTree := docker.NewProcessTreeStub
	defer func() { docker.NewProcessTreeStub = oldProcessTree }()

	docker.NewProcessTreeStub = func(procRoot string) (process.Tree, error) {
		return &mockProcessTree{map[int]int{2: 1}}, nil
	}

	var (
		pid1NodeID       = report.MakeProcessNodeID("somehost.com", "1")
		pid2NodeID       = report.MakeProcessNodeID("somehost.com", "2")
		wantNodeMetadata = report.NodeMetadata{docker.ContainerID: "ping"}
	)

	input := report.MakeReport()
	input.Process.NodeMetadatas[pid1NodeID] = report.NodeMetadata{"pid": "1"}
	input.Process.NodeMetadatas[pid2NodeID] = report.NodeMetadata{"pid": "2"}

	want := report.MakeReport()
	want.Process.NodeMetadatas[pid1NodeID] = report.NodeMetadata{"pid": "1"}.Merge(wantNodeMetadata)
	want.Process.NodeMetadatas[pid2NodeID] = report.NodeMetadata{"pid": "2"}.Merge(wantNodeMetadata)

	tagger := docker.NewTagger(mockRegistryInstance, "/irrelevant")
	have, err := tagger.Tag(input)
	if err != nil {
		t.Errorf("%v", err)
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}
Example #2
0
func TestTagger(t *testing.T) {
	oldProcessTree := docker.NewProcessTreeStub
	defer func() { docker.NewProcessTreeStub = oldProcessTree }()

	docker.NewProcessTreeStub = func(_ process.Walker) (process.Tree, error) {
		return &mockProcessTree{map[int]int{3: 2}}, nil
	}

	var (
		pid1NodeID = report.MakeProcessNodeID("somehost.com", "2")
		pid2NodeID = report.MakeProcessNodeID("somehost.com", "3")
		wantNode   = report.MakeNodeWith(map[string]string{docker.ContainerID: "ping"})
	)

	input := report.MakeReport()
	input.Process.AddNode(pid1NodeID, report.MakeNodeWith(map[string]string{process.PID: "2"}))
	input.Process.AddNode(pid2NodeID, report.MakeNodeWith(map[string]string{process.PID: "3"}))

	want := report.MakeReport()
	want.Process.AddNode(pid1NodeID, report.MakeNodeWith(map[string]string{process.PID: "2"}).Merge(wantNode))
	want.Process.AddNode(pid2NodeID, report.MakeNodeWith(map[string]string{process.PID: "3"}).Merge(wantNode))

	tagger := docker.NewTagger(mockRegistryInstance, nil)
	have, err := tagger.Tag(input)
	if err != nil {
		t.Errorf("%v", err)
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}
Example #3
0
func TestReporter(t *testing.T) {
	walker := &mockWalker{
		processes: []process.Process{
			{PID: 1, PPID: 0, Name: "init"},
			{PID: 2, PPID: 1, Name: "bash"},
			{PID: 3, PPID: 1, Name: "apache", Threads: 2},
			{PID: 4, PPID: 2, Name: "ping", Cmdline: "ping foo.bar.local"},
			{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
		},
	}
	getDeltaTotalJiffies := func() (uint64, float64, error) { return 0, 0., nil }
	now := time.Now()
	mtime.NowForce(now)
	defer mtime.NowReset()

	reporter := process.NewReporter(walker, "", getDeltaTotalJiffies)
	want := report.MakeReport()
	want.Process = report.MakeTopology().AddNode(
		report.MakeProcessNodeID("", "1"), report.MakeNodeWith(map[string]string{
			process.PID:     "1",
			process.Name:    "init",
			process.Threads: "0",
		}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
	).AddNode(
		report.MakeProcessNodeID("", "2"), report.MakeNodeWith(map[string]string{
			process.PID:     "2",
			process.Name:    "bash",
			process.PPID:    "1",
			process.Threads: "0",
		}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
	).AddNode(
		report.MakeProcessNodeID("", "3"), report.MakeNodeWith(map[string]string{
			process.PID:     "3",
			process.Name:    "apache",
			process.PPID:    "1",
			process.Threads: "2",
		}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
	).AddNode(
		report.MakeProcessNodeID("", "4"), report.MakeNodeWith(map[string]string{
			process.PID:     "4",
			process.Name:    "ping",
			process.PPID:    "2",
			process.Cmdline: "ping foo.bar.local",
			process.Threads: "0",
		}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
	).AddNode(
		report.MakeProcessNodeID("", "5"), report.MakeNodeWith(map[string]string{
			process.PID:     "5",
			process.PPID:    "1",
			process.Cmdline: "tail -f /var/log/syslog",
			process.Threads: "0",
		}).WithMetric(process.MemoryUsage, report.MakeMetric().Add(now, 0.)),
	)

	have, err := reporter.Report()
	if err != nil || !reflect.DeepEqual(want, have) {
		t.Errorf("%s (%v)", test.Diff(want, have), err)
	}
}
Example #4
0
func TestReporter(t *testing.T) {
	oldWalk := process.Walk
	defer func() { process.Walk = oldWalk }()

	process.Walk = func(_ string, f func(*process.Process)) error {
		for _, p := range []*process.Process{
			{PID: 1, PPID: 0, Comm: "init"},
			{PID: 2, PPID: 1, Comm: "bash"},
			{PID: 3, PPID: 1, Comm: "apache", Threads: 2},
			{PID: 4, PPID: 2, Comm: "ping", Cmdline: "ping foo.bar.local"},
		} {
			f(p)
		}
		return nil
	}

	reporter := process.NewReporter("", "")
	want := report.MakeReport()
	want.Process = report.Topology{
		Adjacency:     report.Adjacency{},
		EdgeMetadatas: report.EdgeMetadatas{},
		NodeMetadatas: report.NodeMetadatas{
			report.MakeProcessNodeID("", "1"): report.NodeMetadata{
				process.PID:     "1",
				process.Comm:    "init",
				process.Cmdline: "",
				process.Threads: "0",
			},
			report.MakeProcessNodeID("", "2"): report.NodeMetadata{
				process.PID:     "2",
				process.Comm:    "bash",
				process.PPID:    "1",
				process.Cmdline: "",
				process.Threads: "0",
			},
			report.MakeProcessNodeID("", "3"): report.NodeMetadata{
				process.PID:     "3",
				process.Comm:    "apache",
				process.PPID:    "1",
				process.Cmdline: "",
				process.Threads: "2",
			},
			report.MakeProcessNodeID("", "4"): report.NodeMetadata{
				process.PID:     "4",
				process.Comm:    "ping",
				process.PPID:    "2",
				process.Cmdline: "ping foo.bar.local",
				process.Threads: "0",
			},
		},
	}

	have, err := reporter.Report()
	if err != nil || !reflect.DeepEqual(want, have) {
		t.Errorf("%s (%v)", test.Diff(want, have), err)
	}
}
Example #5
0
func TestReporter(t *testing.T) {
	walker := &mockWalker{
		processes: []process.Process{
			{PID: 1, PPID: 0, Comm: "init"},
			{PID: 2, PPID: 1, Comm: "bash"},
			{PID: 3, PPID: 1, Comm: "apache", Threads: 2},
			{PID: 4, PPID: 2, Comm: "ping", Cmdline: "ping foo.bar.local"},
			{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
		},
	}

	reporter := process.NewReporter(walker, "")
	want := report.MakeReport()
	want.Process = report.Topology{
		Adjacency:     report.Adjacency{},
		EdgeMetadatas: report.EdgeMetadatas{},
		NodeMetadatas: report.NodeMetadatas{
			report.MakeProcessNodeID("", "1"): report.MakeNodeMetadataWith(map[string]string{
				process.PID:     "1",
				process.Comm:    "init",
				process.Threads: "0",
			}),
			report.MakeProcessNodeID("", "2"): report.MakeNodeMetadataWith(map[string]string{
				process.PID:     "2",
				process.Comm:    "bash",
				process.PPID:    "1",
				process.Threads: "0",
			}),
			report.MakeProcessNodeID("", "3"): report.MakeNodeMetadataWith(map[string]string{
				process.PID:     "3",
				process.Comm:    "apache",
				process.PPID:    "1",
				process.Threads: "2",
			}),
			report.MakeProcessNodeID("", "4"): report.MakeNodeMetadataWith(map[string]string{
				process.PID:     "4",
				process.Comm:    "ping",
				process.PPID:    "2",
				process.Cmdline: "ping foo.bar.local",
				process.Threads: "0",
			}),
			report.MakeProcessNodeID("", "5"): report.MakeNodeMetadataWith(map[string]string{
				process.PID:     "5",
				process.PPID:    "1",
				process.Cmdline: "tail -f /var/log/syslog",
				process.Threads: "0",
			}),
		},
	}

	have, err := reporter.Report()
	if err != nil || !reflect.DeepEqual(want, have) {
		t.Errorf("%s (%v)", test.Diff(want, have), err)
	}
}
Example #6
0
func TestTagger(t *testing.T) {
	mtime.NowForce(time.Now())
	defer mtime.NowReset()

	oldProcessTree := docker.NewProcessTreeStub
	defer func() { docker.NewProcessTreeStub = oldProcessTree }()

	docker.NewProcessTreeStub = func(_ process.Walker) (process.Tree, error) {
		return &mockProcessTree{map[int]int{3: 2}}, nil
	}

	var (
		pid1NodeID = report.MakeProcessNodeID("somehost.com", "2")
		pid2NodeID = report.MakeProcessNodeID("somehost.com", "3")
	)

	input := report.MakeReport()
	input.Process.AddNode(pid1NodeID, report.MakeNodeWith(map[string]string{process.PID: "2"}))
	input.Process.AddNode(pid2NodeID, report.MakeNodeWith(map[string]string{process.PID: "3"}))

	have, err := docker.NewTagger(mockRegistryInstance, nil).Tag(input)
	if err != nil {
		t.Errorf("%v", err)
	}

	// Processes should be tagged with their container ID, and parents
	for _, nodeID := range []string{pid1NodeID, pid2NodeID} {
		node, ok := have.Process.Nodes[nodeID]
		if !ok {
			t.Errorf("Expected process node %s, but not found", nodeID)
		}

		// node should have the container id added
		if have, ok := node.Latest.Lookup(docker.ContainerID); !ok || have != "ping" {
			t.Errorf("Expected process node %s to have container id %q, got %q", nodeID, "ping", have)
		}

		// node should have the container as a parent
		if have, ok := node.Parents.Lookup(report.Container); !ok || !have.Contains(report.MakeContainerNodeID("ping")) {
			t.Errorf("Expected process node %s to have container %q as a parent, got %q", nodeID, "ping", have)
		}

		// node should have the container image as a parent
		if have, ok := node.Parents.Lookup(report.ContainerImage); !ok || !have.Contains(report.MakeContainerImageNodeID("baz")) {
			t.Errorf("Expected process node %s to have container image %q as a parent, got %q", nodeID, "baz", have)
		}
	}
}
Example #7
0
func TestEdgeID(t *testing.T) {
	for _, bad := range []string{
		client54001EndpointNodeID,
		client54002EndpointNodeID,
		unknown1EndpointNodeID,
		unknown2EndpointNodeID,
		unknown3EndpointNodeID,
		clientAddressNodeID,
		serverAddressNodeID,
		unknownAddressNodeID,
		clientHostNodeID,
		serverHostNodeID,
		">1.2.3.4",
		">",
		";",
		"",
	} {
		if srcNodeID, dstNodeID, ok := report.ParseEdgeID(bad); ok {
			t.Errorf("%q: expected failure, but got (%q, %q)", bad, srcNodeID, dstNodeID)
		}
	}

	for input, want := range map[string]struct{ srcNodeID, dstNodeID string }{
		report.MakeEdgeID("a", report.MakeEndpointNodeID("a", "b", "c")): {"a", report.MakeEndpointNodeID("a", "b", "c")},
		report.MakeEdgeID("a", report.MakeAddressNodeID("a", "b")):       {"a", report.MakeAddressNodeID("a", "b")},
		report.MakeEdgeID("a", report.MakeProcessNodeID("a", "b")):       {"a", report.MakeProcessNodeID("a", "b")},
		report.MakeEdgeID("a", report.MakeHostNodeID("a")):               {"a", report.MakeHostNodeID("a")},
		"host.com|1.2.3.4":                                               {"host.com", "1.2.3.4"},
		"a|b;c":                                                          {"a", "b;c"},
		"a|b":                                                            {"a", "b"},
		"a|":                                                             {"a", ""},
		"|b":                                                             {"", "b"},
		"|":                                                              {"", ""},
	} {
		srcNodeID, dstNodeID, ok := report.ParseEdgeID(input)
		if !ok {
			t.Errorf("%q: not OK", input)
			continue
		}
		if want, have := want.srcNodeID, srcNodeID; want != have {
			t.Errorf("%q: want %q, have %q", input, want, have)
		}
		if want, have := want.dstNodeID, dstNodeID; want != have {
			t.Errorf("%q: want %q, have %q", input, want, have)
		}
	}
}
Example #8
0
// MapEndpoint2Process maps endpoint Nodes to process
// Nodes.
//
// If this function is given a pseudo node, then it will just return it;
// Pseudo nodes will never have pids in them, and therefore will never
// be able to be turned into a Process node.
//
// Otherwise, this function will produce a node with the correct ID
// format for a process, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a process graph to get that info.
func MapEndpoint2Process(n report.Node, local report.Networks) report.Nodes {
	// Nodes without a hostid are treated as pseudo nodes
	if _, ok := n.Latest.Lookup(report.HostNodeID); !ok {
		return MapEndpoint2Pseudo(n, local)
	}

	pid, timestamp, ok := n.Latest.LookupEntry(process.PID)
	if !ok {
		return report.Nodes{}
	}

	id := report.MakeProcessNodeID(report.ExtractHostID(n), pid)
	node := NewDerivedNode(id, n).WithTopology(report.Process)
	node.Latest = node.Latest.Set(process.PID, timestamp, pid)
	node.Counters = node.Counters.Add(n.Topology, 1)
	return report.Nodes{id: node}
}
Example #9
0
func (r *Reporter) processTopology() (report.Topology, error) {
	t := report.NewTopology()
	err := r.walker.Walk(func(p Process) {
		pidstr := strconv.Itoa(p.PID)
		nodeID := report.MakeProcessNodeID(r.scope, pidstr)
		t.NodeMetadatas[nodeID] = report.MakeNodeMetadataWith(map[string]string{
			PID:     pidstr,
			Comm:    p.Comm,
			Cmdline: p.Cmdline,
			Threads: strconv.Itoa(p.Threads),
		})
		if p.PPID > 0 {
			t.NodeMetadatas[nodeID].Metadata[PPID] = strconv.Itoa(p.PPID)
		}
	})

	return t, err
}
Example #10
0
func (r *reporter) processTopology() (report.Topology, error) {
	t := report.NewTopology()
	err := Walk(r.procRoot, func(p *Process) {
		pidstr := strconv.Itoa(p.PID)
		nodeID := report.MakeProcessNodeID(r.scope, pidstr)
		t.NodeMetadatas[nodeID] = report.NodeMetadata{
			PID:     pidstr,
			Comm:    p.Comm,
			Cmdline: p.Cmdline,
			Threads: strconv.Itoa(p.Threads),
		}
		if p.PPID > 0 {
			t.NodeMetadatas[nodeID][PPID] = strconv.Itoa(p.PPID)
		}
	})

	return t, err
}
Example #11
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
}
Example #12
0
func (r *Reporter) processTopology() (report.Topology, error) {
	t := report.MakeTopology()
	err := r.walker.Walk(func(p Process) {
		pidstr := strconv.Itoa(p.PID)
		nodeID := report.MakeProcessNodeID(r.scope, pidstr)
		t.Nodes[nodeID] = report.MakeNode()
		for _, tuple := range []struct{ key, value string }{
			{PID, pidstr},
			{Comm, p.Comm},
			{Cmdline, p.Cmdline},
			{Threads, strconv.Itoa(p.Threads)},
		} {
			if tuple.value != "" {
				t.Nodes[nodeID].Metadata[tuple.key] = tuple.value
			}
		}
		if p.PPID > 0 {
			t.Nodes[nodeID].Metadata[PPID] = strconv.Itoa(p.PPID)
		}
	})

	return t, err
}
Example #13
0
	NonContainerComm = "bash"

	ClientHostNodeID = report.MakeHostNodeID(ClientHostID)
	ServerHostNodeID = report.MakeHostNodeID(ServerHostID)

	Client54001NodeID    = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54001)            // curl (1)
	Client54002NodeID    = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54002)            // curl (2)
	Server80NodeID       = report.MakeEndpointNodeID(ServerHostID, ServerIP, ServerPort)                 // apache
	UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient1IP, UnknownClient1Port) // we want to ensure two unknown clients, connnected
	UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient2IP, UnknownClient2Port) // to the same server, are deduped.
	UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient3IP, UnknownClient3Port) // Check this one isn't deduped
	RandomClientNodeID   = report.MakeEndpointNodeID(ServerHostID, RandomClientIP, RandomClientPort)     // this should become an internet node
	NonContainerNodeID   = report.MakeEndpointNodeID(ServerHostID, ServerIP, NonContainerClientPort)
	GoogleEndpointNodeID = report.MakeEndpointNodeID(ServerHostID, GoogleIP, GooglePort)

	ClientProcess1NodeID      = report.MakeProcessNodeID(ClientHostID, Client1PID)
	ClientProcess2NodeID      = report.MakeProcessNodeID(ClientHostID, Client2PID)
	ServerProcessNodeID       = report.MakeProcessNodeID(ServerHostID, ServerPID)
	NonContainerProcessNodeID = report.MakeProcessNodeID(ServerHostID, NonContainerPID)

	ClientContainerID     = "a1b2c3d4e5"
	ServerContainerID     = "5e4d3c2b1a"
	ClientContainerNodeID = report.MakeContainerNodeID(ClientHostID, ClientContainerID)
	ServerContainerNodeID = report.MakeContainerNodeID(ServerHostID, ServerContainerID)

	ClientContainerImageID     = "imageid123"
	ServerContainerImageID     = "imageid456"
	ClientContainerImageNodeID = report.MakeContainerNodeID(ClientHostID, ClientContainerImageID)
	ServerContainerImageNodeID = report.MakeContainerNodeID(ServerHostID, ServerContainerImageID)
	ClientContainerImageName   = "image/client"
	ServerContainerImageName   = "image/server"
Example #14
0
func TestReporter(t *testing.T) {
	processes := []process.Process{
		{PID: 1, PPID: 0, Name: "init"},
		{PID: 2, PPID: 1, Name: "bash"},
		{PID: 3, PPID: 1, Name: "apache", Threads: 2},
		{PID: 4, PPID: 2, Name: "ping", Cmdline: "ping foo.bar.local"},
		{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
	}
	walker := &mockWalker{processes: processes}
	getDeltaTotalJiffies := func() (uint64, float64, error) { return 0, 0., nil }
	now := time.Now()
	mtime.NowForce(now)
	defer mtime.NowReset()

	rpt, err := process.NewReporter(walker, "", getDeltaTotalJiffies).Report()
	if err != nil {
		t.Error(err)
	}

	// It reports the init process
	node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "1")]
	if !ok {
		t.Errorf("Expected report to include the pid 1 init")
	}
	if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[0].Name {
		t.Errorf("Expected %q got %q", processes[0].Name, name)
	}

	// It reports plain processes (with parent pid, and metrics)
	node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "2")]
	if !ok {
		t.Errorf("Expected report to include the pid 2 bash")
	}
	if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[1].Name {
		t.Errorf("Expected %q got %q", processes[1].Name, name)
	}
	if ppid, ok := node.Latest.Lookup(process.PPID); !ok || ppid != fmt.Sprint(processes[1].PPID) {
		t.Errorf("Expected %d got %q", processes[1].PPID, ppid)
	}
	if memoryUsage, ok := node.Metrics[process.MemoryUsage]; !ok {
		t.Errorf("Expected memory usage metric, but not found")
	} else if sample := memoryUsage.LastSample(); sample == nil {
		t.Errorf("Expected memory usage metric to have a sample, but there were none")
	} else if sample.Value != 0. {
		t.Errorf("Expected memory usage metric sample %f, got %f", 0., sample.Value)
	}

	// It reports thread-counts
	node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "3")]
	if !ok {
		t.Errorf("Expected report to include the pid 3 apache")
	}
	if threads, ok := node.Latest.Lookup(process.Threads); !ok || threads != fmt.Sprint(processes[2].Threads) {
		t.Errorf("Expected %d got %q", processes[2].Threads, threads)
	}

	// It reports the Cmdline
	node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "4")]
	if !ok {
		t.Errorf("Expected report to include the pid 4 ping")
	}
	if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[3].Cmdline) {
		t.Errorf("Expected %q got %q", processes[3].Cmdline, cmdline)
	}

	// It reports processes without a Name
	node, ok = rpt.Process.Nodes[report.MakeProcessNodeID("", "5")]
	if !ok {
		t.Errorf("Expected report to include the pid 5 tail")
	}
	if name, ok := node.Latest.Lookup(process.Name); ok {
		t.Errorf("Expected no name, but got %q", name)
	}
	if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[4].Cmdline) {
		t.Errorf("Expected %q got %q", processes[4].Cmdline, cmdline)
	}
}