github.com/kiali/kiali@v1.84.0/graph/telemetry/istio/appender/throughput.go (about) 1 package appender 2 3 import ( 4 "fmt" 5 "math" 6 "time" 7 8 "github.com/prometheus/common/model" 9 10 "github.com/kiali/kiali/graph" 11 "github.com/kiali/kiali/graph/telemetry/istio/util" 12 "github.com/kiali/kiali/log" 13 "github.com/kiali/kiali/prometheus" 14 ) 15 16 const ( 17 // ThroughputAppenderName uniquely identifies the appender: throughput 18 ThroughputAppenderName = "throughput" 19 ) 20 21 // ThroughputAppender is responsible for adding throughput information to the graph. Throughput 22 // is represented as bytes/sec. Throughput may be for request bytes or response bytes depending 23 // on the options. Request throughput will be reported using source telemetry, response throughput 24 // using destination telemetry. 25 // Name: throughput 26 type ThroughputAppender struct { 27 GraphType string 28 InjectServiceNodes bool 29 Namespaces graph.NamespaceInfoMap 30 QueryTime int64 // unix time in seconds 31 Rates graph.RequestedRates 32 ThroughputType string 33 } 34 35 // Name implements Appender 36 func (a ThroughputAppender) Name() string { 37 return ThroughputAppenderName 38 } 39 40 // IsFinalizer implements Appender 41 func (a ThroughputAppender) IsFinalizer() bool { 42 return false 43 } 44 45 // AppendGraph implements Appender 46 func (a ThroughputAppender) AppendGraph(trafficMap graph.TrafficMap, globalInfo *graph.AppenderGlobalInfo, namespaceInfo *graph.AppenderNamespaceInfo) { 47 if len(trafficMap) == 0 { 48 return 49 } 50 51 // HTTP Throughput only apply to HTTP request traffic 52 if a.Rates.Http != graph.RateRequests { 53 return 54 } 55 56 if globalInfo.PromClient == nil { 57 var err error 58 globalInfo.PromClient, err = prometheus.NewClient() 59 graph.CheckError(err) 60 } 61 62 a.appendGraph(trafficMap, namespaceInfo.Namespace, globalInfo.PromClient) 63 } 64 65 func (a ThroughputAppender) appendGraph(trafficMap graph.TrafficMap, namespace string, client *prometheus.Client) { 66 log.Tracef("Generating [%s] throughput; namespace = %v", a.ThroughputType, namespace) 67 68 // create map to quickly look up throughput 69 throughputMap := make(map[string]float64) 70 duration := a.Namespaces[namespace].Duration 71 72 // query prometheus for throughput info in two queries: 73 groupBy := "source_cluster,source_workload_namespace,source_workload,source_canonical_service,source_canonical_revision,destination_cluster,destination_service_namespace,destination_service,destination_service_name,destination_workload_namespace,destination_workload,destination_canonical_service,destination_canonical_revision" 74 metric := fmt.Sprintf("istio_%s_bytes_sum", a.ThroughputType) 75 reporter := "destination" 76 if a.ThroughputType == "request" { 77 reporter = "source" 78 } 79 80 // query prometheus for throughput rates in two queries: 81 // 1) query for requests originating from a workload outside the namespace. 82 query := fmt.Sprintf(`sum(rate(%s{reporter="%s",source_workload_namespace!="%s",destination_service_namespace="%s"}[%vs])) by (%s) > 0`, 83 metric, 84 reporter, 85 namespace, 86 namespace, 87 int(duration.Seconds()), // range duration for the query 88 groupBy) 89 vector := promQuery(query, time.Unix(a.QueryTime, 0), client.GetContext(), client.API(), a) 90 a.populateThroughputMap(throughputMap, &vector) 91 92 // 2) query for requests originating from a workload inside of the namespace 93 query = fmt.Sprintf(`sum(rate(%s{reporter="%s",source_workload_namespace="%s"}[%vs])) by (%s) > 0`, 94 metric, 95 reporter, 96 namespace, 97 int(duration.Seconds()), // range duration for the query 98 groupBy) 99 vector = promQuery(query, time.Unix(a.QueryTime, 0), client.GetContext(), client.API(), a) 100 a.populateThroughputMap(throughputMap, &vector) 101 102 applyThroughput(trafficMap, throughputMap) 103 } 104 105 func applyThroughput(trafficMap graph.TrafficMap, throughputMap map[string]float64) { 106 for _, n := range trafficMap { 107 for _, e := range n.Edges { 108 key := fmt.Sprintf("%s %s %s", e.Source.ID, e.Dest.ID, e.Metadata[graph.ProtocolKey].(string)) 109 if val, ok := throughputMap[key]; ok { 110 e.Metadata[graph.Throughput] = val 111 } 112 } 113 } 114 } 115 116 func (a ThroughputAppender) populateThroughputMap(throughputMap map[string]float64, vector *model.Vector) { 117 for _, s := range *vector { 118 m := s.Metric 119 lSourceCluster, sourceClusterOk := m["source_cluster"] 120 lSourceWlNs, sourceWlNsOk := m["source_workload_namespace"] 121 lSourceWl, sourceWlOk := m["source_workload"] 122 lSourceApp, sourceAppOk := m["source_canonical_service"] 123 lSourceVer, sourceVerOk := m["source_canonical_revision"] 124 lDestCluster, destClusterOk := m["destination_cluster"] 125 lDestSvcNs, destSvcNsOk := m["destination_service_namespace"] 126 lDestSvc, destSvcOk := m["destination_service"] 127 lDestSvcName, destSvcNameOk := m["destination_service_name"] 128 lDestWlNs, destWlNsOk := m["destination_workload_namespace"] 129 lDestWl, destWlOk := m["destination_workload"] 130 lDestApp, destAppOk := m["destination_canonical_service"] 131 lDestVer, destVerOk := m["destination_canonical_revision"] 132 133 if !sourceWlNsOk || !sourceWlOk || !sourceAppOk || !sourceVerOk || !destSvcNsOk || !destSvcNameOk || !destSvcOk || !destWlNsOk || !destWlOk || !destAppOk || !destVerOk { 134 log.Warningf("populateThroughputMap: Skipping %s, missing expected labels", m.String()) 135 continue 136 } 137 138 sourceWlNs := string(lSourceWlNs) 139 sourceWl := string(lSourceWl) 140 sourceApp := string(lSourceApp) 141 sourceVer := string(lSourceVer) 142 destSvc := string(lDestSvc) 143 144 // handle clusters 145 sourceCluster, destCluster := util.HandleClusters(lSourceCluster, sourceClusterOk, lDestCluster, destClusterOk) 146 147 if util.IsBadSourceTelemetry(sourceCluster, sourceClusterOk, sourceWlNs, sourceWl, sourceApp) { 148 continue 149 } 150 151 val := float64(s.Value) 152 153 // handle unusual destinations 154 destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer, _ := util.HandleDestination(sourceCluster, sourceWlNs, sourceWl, destCluster, string(lDestSvcNs), string(lDestSvc), string(lDestSvcName), string(lDestWlNs), string(lDestWl), string(lDestApp), string(lDestVer)) 155 156 if util.IsBadDestTelemetry(destCluster, destClusterOk, destSvcNs, destSvc, destSvcName, destWl) { 157 continue 158 } 159 160 // Should not happen but if NaN for any reason, Just skip it 161 if math.IsNaN(val) { 162 continue 163 } 164 165 // don't inject a service node if any of: 166 // - destSvcName is not set 167 // - destSvcName is PassthroughCluster (see https://github.com/kiali/kiali/issues/4488) 168 // - dest node is already a service node 169 inject := false 170 if a.InjectServiceNodes && graph.IsOK(destSvcName) && destSvcName != graph.PassthroughCluster { 171 _, destNodeType, err := graph.Id(destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer, a.GraphType) 172 if err != nil { 173 log.Warningf("Skipping (t) %s, %s", m.String(), err) 174 continue 175 } 176 inject = (graph.NodeTypeService != destNodeType) 177 } 178 179 if inject { 180 // Only set throughput on the outgoing edge. On the incoming edge, we can't validly aggregate thoughputs of the outgoing edges 181 // - analogous to https://issues.redhat.com/browse/KIALI-2297, we can't assume even distribution 182 a.addThroughput(throughputMap, val, destCluster, destSvcNs, destSvcName, "", "", "", destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer) 183 } else { 184 a.addThroughput(throughputMap, val, sourceCluster, sourceWlNs, "", sourceWl, sourceApp, sourceVer, destCluster, destSvcNs, destSvcName, destWlNs, destWl, destApp, destVer) 185 } 186 } 187 } 188 189 func (a ThroughputAppender) addThroughput(throughputMap map[string]float64, val float64, sourceCluster, sourceNs, sourceSvc, sourceWl, sourceApp, sourceVer, destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer string) { 190 sourceID, _, err := graph.Id(sourceCluster, sourceNs, sourceSvc, sourceNs, sourceWl, sourceApp, sourceVer, a.GraphType) 191 if err != nil { 192 log.Warningf("Skipping addThroughput (source), %s", err) 193 return 194 } 195 destID, _, err := graph.Id(destCluster, destSvcNs, destSvc, destWlNs, destWl, destApp, destVer, a.GraphType) 196 if err != nil { 197 log.Warningf("Skipping addThroughput (dest), %s", err) 198 return 199 } 200 key := fmt.Sprintf("%s %s http", sourceID, destID) 201 202 throughputMap[key] += val 203 }