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 }