github.com/kiali/kiali@v1.84.0/graph/telemetry/common.go (about)

     1  // Package telemtry contains telemetry provider implementations as well as common code that can be
     2  // shared by each telemetry vendor.  Istio vendor is the canonical impl.
     3  package telemetry
     4  
     5  import (
     6  	"fmt"
     7  
     8  	"github.com/kiali/kiali/graph"
     9  	"github.com/kiali/kiali/log"
    10  )
    11  
    12  // MergeTrafficMaps typically combines two namespace traffic maps. It ensures that we only
    13  // have unique nodes by removing duplicate nodes and merging their edges.  When removing a
    14  // duplicate prefer an instance from the namespace being merged-in because it is guaranteed
    15  // to have all appender information applied (i.e. not an outsider). We also need to avoid duplicate
    16  // edges, it can happen when a terminal node of one namespace is a root node of another:
    17  // - ns1 graph: unknown -> ns1:A -> ns2:B
    18  // - ns2 graph:   ns1:A -> ns2:B -> ns2:C
    19  func MergeTrafficMaps(trafficMap graph.TrafficMap, ns string, nsTrafficMap graph.TrafficMap) {
    20  	for nsID, nsNode := range nsTrafficMap {
    21  		if node, isDup := trafficMap[nsID]; isDup {
    22  			if nsNode.Namespace == ns {
    23  				// prefer nsNode (see above comment), so do a swap
    24  				trafficMap[nsID] = nsNode
    25  				temp := node
    26  				node = nsNode
    27  				nsNode = temp
    28  			}
    29  			for _, nsEdge := range nsNode.Edges {
    30  				isDupEdge := false
    31  				for _, e := range node.Edges {
    32  					if nsEdge.Dest.ID == e.Dest.ID && nsEdge.Metadata[graph.ProtocolKey] == e.Metadata[graph.ProtocolKey] {
    33  						isDupEdge = true
    34  						break
    35  					}
    36  				}
    37  				if !isDupEdge {
    38  					node.Edges = append(node.Edges, nsEdge)
    39  					// add traffic for the new edge
    40  					graph.AddOutgoingEdgeToMetadata(node.Metadata, nsEdge.Metadata)
    41  				}
    42  			}
    43  		} else {
    44  			trafficMap[nsID] = nsNode
    45  		}
    46  	}
    47  }
    48  
    49  // ReduceToServiceGraph compresses a [service-injected workload] graph by removing
    50  // the workload nodes such that, with exception of non-service root nodes, the resulting
    51  // graph has edges only from and to service nodes.  It is typically the last thing called
    52  // prior to returning the service graph.
    53  func ReduceToServiceGraph(trafficMap graph.TrafficMap) graph.TrafficMap {
    54  	reducedTrafficMap := graph.NewTrafficMap()
    55  
    56  	for id, n := range trafficMap {
    57  		if n.NodeType != graph.NodeTypeService {
    58  			// if node isRoot then keep it to better understand traffic flow.
    59  			if val, ok := n.Metadata[graph.IsRoot]; ok && val.(bool) {
    60  				// Remove any edge to a non-service node.  The service graph only shows non-service root
    61  				// nodes, all other nodes are service nodes.  The use case is direct workload-to-workload
    62  				// traffic, which is unusual but possible.  This can lead to nodes with outgoing traffic
    63  				// not represented by an outgoing edge, but that is the nature of the graph type.
    64  				serviceEdges := []*graph.Edge{}
    65  				for _, e := range n.Edges {
    66  					if e.Dest.NodeType == graph.NodeTypeService {
    67  						serviceEdges = append(serviceEdges, e)
    68  					} else {
    69  						log.Tracef("Service graph ignoring non-service root destination [%s]", e.Dest.Workload)
    70  					}
    71  				}
    72  				// if there are no outgoing edges to a service then ignore the node
    73  				if len(serviceEdges) == 0 {
    74  					log.Tracef("Service graph ignoring non-service root [%s]", n.Workload)
    75  					continue
    76  				}
    77  				// reset the outgoing traffic and add the surviving edge metadata
    78  				graph.ResetOutgoingMetadata(n.Metadata)
    79  				for _, edgeToService := range serviceEdges {
    80  					graph.AddOutgoingEdgeToMetadata(n.Metadata, edgeToService.Metadata)
    81  				}
    82  				n.Edges = serviceEdges
    83  				reducedTrafficMap[id] = n
    84  			}
    85  			continue
    86  		}
    87  
    88  		// now, handle a service node, add to reduced traffic map, generate new edges, and reset outgoing
    89  		// traffic to just that traffic to other services.
    90  		reducedTrafficMap[id] = n
    91  
    92  		// reset outgoing traffic for the service node.   Terminating traffic is lost but that is the nature
    93  		// of the graph, which aims to show service-to-service interaction.
    94  		graph.ResetOutgoingMetadata(n.Metadata)
    95  
    96  		// eliminate the edges to workload nodes, resetting their outgoing edges to the source service
    97  		workloadEdges := n.Edges
    98  		n.Edges = []*graph.Edge{} // reset source service edges
    99  		for _, edgeToWorkload := range workloadEdges {
   100  			destWorkload := edgeToWorkload.Dest
   101  			checkNodeType(graph.NodeTypeWorkload, destWorkload)
   102  			for _, edgeToService := range destWorkload.Edges {
   103  				// As above, ignore edges to non-service destinations
   104  				if edgeToService.Dest.NodeType != graph.NodeTypeService {
   105  					log.Tracef("Service graph ignoring non-service destination [%s:%s]", edgeToService.Dest.NodeType, edgeToService.Dest.Workload)
   106  					continue
   107  				}
   108  				destService := edgeToService.Dest
   109  				var edge *graph.Edge
   110  				for _, e := range n.Edges {
   111  					if destService.ID == e.Dest.ID && edgeToService.Metadata[graph.ProtocolKey] == e.Metadata[graph.ProtocolKey] {
   112  						edge = e
   113  						break
   114  					}
   115  				}
   116  				if nil == edge {
   117  					edgeToService.Source = n
   118  					n.Edges = append(n.Edges, edgeToService)
   119  					graph.AddOutgoingEdgeToMetadata(n.Metadata, edgeToService.Metadata)
   120  				} else {
   121  					addServiceGraphTraffic(edge, edgeToService)
   122  				}
   123  			}
   124  		}
   125  	}
   126  
   127  	return reducedTrafficMap
   128  }
   129  
   130  func addServiceGraphTraffic(toEdge, fromEdge *graph.Edge) {
   131  	graph.AddOutgoingEdgeToMetadata(toEdge.Source.Metadata, fromEdge.Metadata)
   132  	graph.AggregateEdgeTraffic(fromEdge, toEdge)
   133  
   134  	// handle any appender-based edge data (nothing currently)
   135  	// note: We used to average response times of the aggregated edges but realized that
   136  	// we can't average quantiles (kiali-2297).
   137  }
   138  
   139  func checkNodeType(expected string, n *graph.Node) {
   140  	if expected != n.NodeType {
   141  		graph.Error(fmt.Sprintf("Expected nodeType [%s] for node [%+v]", expected, n))
   142  	}
   143  }