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  }