<commit_before>package render

import (
	"regexp"

	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/report"
)

// Constants are used in the tests.
const (
	UncontainedID    = "uncontained"
	UncontainedMajor = "Uncontained"

	// Topology for IPs so we can differentiate them at the end
	IP = "IP"
)

// UncontainedIDPrefix is the prefix of uncontained pseudo nodes
var UncontainedIDPrefix = MakePseudoNodeID(UncontainedID, "")

// ContainerRenderer is a Renderer which produces a renderable container
// graph by merging the process graph and the container topology.
// NB We only want processes in container _or_ processes with network connections
// but we need to be careful to ensure we only include each edge once, by only
// including the ProcessRenderer once.
var ContainerRenderer = Memoise(MakeFilter(
	func(n report.Node) bool {
		// Drop deleted containers
		state, ok := n.Latest.Lookup(docker.ContainerState)
		return !ok || state != docker.StateDeleted
	},
	MakeReduce(
		MakeMap(
			MapProcess2Container,
			ProcessRenderer,
		),
		ConnectionJoin(MapContainer2IP, report.Container),
	),
))

const originalNodeID = "original_node_id"

// ConnectionJoin joins the given topology with connections from the
// endpoints topology, using the toIPs function to extract IPs from
// the nodes.
func ConnectionJoin(toIPs func(report.Node) []string, topology string) Renderer {
	return connectionJoin{toIPs: toIPs, topology: topology}
}

type connectionJoin struct {
	toIPs    func(report.Node) []string
	topology string
}

func (c connectionJoin) Render(rpt report.Report) Nodes {
	inputNodes := TopologySelector(c.topology).Render(rpt).Nodes
	// Collect all the IPs we are trying to map to, and which ID they map from
	var ipNodes = map[string]string{}
	for _, n := range inputNodes {
		for _, ip := range c.toIPs(n) {
			if _, exists := ipNodes[ip]; exists {
				// If an IP is shared between multiple nodes, we can't reliably
				// attribute an connection based on its IP
				ipNodes[ip] = "" // blank out the mapping so we don't use it
			} else {
				ipNodes[ip] = n.ID
			}
		}
	}
	return MapEndpoints(
		func(m report.Node) string {
			scope, addr, port, ok := report.ParseEndpointNodeID(m.ID)
			if !ok {
				return ""
			}
			id, found := ipNodes[report.MakeScopedEndpointNodeID(scope, addr, "")]
			// We also allow for joining on ip:port pairs.  This is
			// useful for connections to the host IPs which have been
			// port mapped to a container can only be unambiguously
			// identified with the port.
			if !found {
				id, found = ipNodes[report.MakeScopedEndpointNodeID(scope, addr, port)]
			}
			if found && id != "" { // not one we blanked out earlier
				// We are guaranteed to find the id, so really this
				// should never end up creating a node.
				return id
			}
			return ""
		}, c.topology).Render(rpt)
}

// FilterEmpty is a Renderer which filters out nodes which have no children<commit_msg>tiny tidy-up / refactor<commit_after>package render

import (
	"regexp"

	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/report"
)

// Constants are used in the tests.
const (
	UncontainedID    = "uncontained"
	UncontainedMajor = "Uncontained"

	// Topology for IPs so we can differentiate them at the end
	IP = "IP"
)

// UncontainedIDPrefix is the prefix of uncontained pseudo nodes
var UncontainedIDPrefix = MakePseudoNodeID(UncontainedID, "")

// ContainerRenderer is a Renderer which produces a renderable container
// graph by merging the process graph and the container topology.
// NB We only want processes in container _or_ processes with network connections
// but we need to be careful to ensure we only include each edge once, by only
// including the ProcessRenderer once.
var ContainerRenderer = Memoise(MakeFilter(
	func(n report.Node) bool {
		// Drop deleted containers
		state, ok := n.Latest.Lookup(docker.ContainerState)
		return !ok || state != docker.StateDeleted
	},
	MakeReduce(
		MakeMap(
			MapProcess2Container,
			ProcessRenderer,
		),
		ConnectionJoin(MapContainer2IP, report.Container),
	),
))

const originalNodeID = "original_node_id"

// ConnectionJoin joins the given topology with connections from the
// endpoints topology, using the toIPs function to extract IPs from
// the nodes.
func ConnectionJoin(toIPs func(report.Node) []string, topology string) Renderer {
	return connectionJoin{toIPs: toIPs, topology: topology}
}

type connectionJoin struct {
	toIPs    func(report.Node) []string
	topology string
}

func (c connectionJoin) Render(rpt report.Report) Nodes {
	inputNodes := TopologySelector(c.topology).Render(rpt).Nodes
	// Collect all the IPs we are trying to map to, and which ID they map from
	var ipNodes = map[string]string{}
	for _, n := range inputNodes {
		for _, ip := range c.toIPs(n) {
			if _, exists := ipNodes[ip]; exists {
				// If an IP is shared between multiple nodes, we can't reliably
				// attribute an connection based on its IP
				ipNodes[ip] = "" // blank out the mapping so we don't use it
			} else {
				ipNodes[ip] = n.ID
			}
		}
	}
	return MapEndpoints(
		func(m report.Node) string {
			scope, addr, port, ok := report.ParseEndpointNodeID(m.ID)
			if !ok {
				return ""
			}
			id, found := ipNodes[report.MakeScopedEndpointNodeID(scope, addr, "")]
			// We also allow for joining on ip:port pairs.  This is
			// useful for connections to the host IPs which have been
			// port mapped to a container can only be unambiguously
			// identified with the port.
			if !found {
				id, found = ipNodes[report.MakeScopedEndpointNodeID(scope, addr, port)]
			}
			if !found || id == "" {
				return ""
			}
			// Not an IP we blanked out earlier.
			//
			// MapEndpoints is guaranteed to find a node with this id
			// (and hence not have to create one), since we got the id
			// from ipNodes, which is populated from c.topology, which
			// is where MapEndpoints will look.
			return id
		}, c.topology).Render(rpt)
}

// FilterEmpty is a Renderer which filters out nodes which have no children
{'fraction_non_alphanumeric': 0.09364161849710982, 'fraction_numerical': 0.0006606110652353427, 'mean_word_length': 3.5397301349325336, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4781443', 'n_tokens_mistral': 2004, 'n_tokens_neox': 1795, 'n_words': 674}