github.com/kiali/kiali@v1.84.0/business/metrics_test.go (about)

     1  package business
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/prometheus/common/model"
     9  	"github.com/stretchr/testify/assert"
    10  
    11  	"github.com/kiali/kiali/config"
    12  	"github.com/kiali/kiali/models"
    13  	"github.com/kiali/kiali/prometheus"
    14  	"github.com/kiali/kiali/prometheus/prometheustest"
    15  )
    16  
    17  func setupMocked() (*MetricsService, *prometheustest.PromAPIMock, error) {
    18  	config.Set(config.NewConfig())
    19  	api := new(prometheustest.PromAPIMock)
    20  	client, err := prometheus.NewClient()
    21  	if err != nil {
    22  		return nil, nil, err
    23  	}
    24  	client.Inject(api)
    25  	return NewMetricsService(client), api, nil
    26  }
    27  
    28  func TestGetServiceMetrics(t *testing.T) {
    29  	assert := assert.New(t)
    30  	srv, api, err := setupMocked()
    31  	if err != nil {
    32  		t.Error(err)
    33  		return
    34  	}
    35  
    36  	q := models.IstioMetricsQuery{
    37  		Namespace: "bookinfo",
    38  		Service:   "productpage",
    39  	}
    40  	q.FillDefaults()
    41  	q.Direction = "inbound"
    42  	q.RateInterval = "5m"
    43  	q.Quantiles = []string{"0.99"}
    44  
    45  	labels := `reporter="source",destination_service_name="productpage",destination_service_namespace="bookinfo"`
    46  	api.MockRange("sum(rate(istio_requests_total{"+labels+"}[5m]))", 2.5)
    47  	api.MockRangeErr("sum(rate(istio_requests_total{"+labels+`,response_code=~"^0$|^[4-5]\\d\\d$"}[5m])) OR sum(rate(istio_requests_total{`+labels+`,grpc_response_status=~"^[1-9]$|^1[0-6]$",response_code!~"^0$|^[4-5]\\d\\d$"}[5m]))`, 4.5)
    48  	api.MockRange("sum(rate(istio_request_bytes_sum{"+labels+"}[5m]))", 1000)
    49  	api.MockRange("sum(rate(istio_response_bytes_sum{"+labels+"}[5m]))", 1001)
    50  	api.MockRange("sum(rate(istio_request_messages_total{"+labels+"}[5m]))", 10)
    51  	api.MockRange("sum(rate(istio_response_messages_total{"+labels+"}[5m]))", 20)
    52  	api.MockRange("sum(rate(istio_tcp_received_bytes_total{"+labels+"}[5m]))", 11)
    53  	api.MockRange("sum(rate(istio_tcp_sent_bytes_total{"+labels+"}[5m]))", 13)
    54  	api.MockRange("sum(rate(istio_tcp_connections_closed_total{"+labels+"}[5m]))", 31)
    55  	api.MockRange("sum(rate(istio_tcp_connections_opened_total{"+labels+"}[5m]))", 32)
    56  	api.MockHistoRange("istio_request_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.7)
    57  	api.MockHistoRange("istio_request_duration_seconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.8)
    58  	api.MockHistoRange("istio_request_duration_milliseconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.8)
    59  	api.MockHistoRange("istio_response_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.9)
    60  
    61  	// Test that range and rate interval are changed when needed (namespace bounds)
    62  	metrics, err := srv.GetMetrics(q, nil)
    63  
    64  	assert.Nil(err)
    65  	assert.Equal(13, len(metrics))
    66  	grpcRecIn := metrics["grpc_received"]
    67  	assert.NotNil(grpcRecIn)
    68  	grpcSentIn := metrics["grpc_sent"]
    69  	assert.NotNil(grpcSentIn)
    70  	rqCountIn := metrics["request_count"]
    71  	assert.NotNil(rqCountIn)
    72  	rqErrorCountIn := metrics["request_error_count"]
    73  	assert.NotNil(rqErrorCountIn)
    74  	rqThroughput := metrics["request_throughput"]
    75  	assert.NotNil(rqThroughput)
    76  	rsThroughput := metrics["response_throughput"]
    77  	assert.NotNil(rsThroughput)
    78  	rqSizeIn := metrics["request_size"]
    79  	assert.NotNil(rqSizeIn)
    80  	rqDurationMillisIn := metrics["request_duration_millis"]
    81  	assert.NotNil(rqDurationMillisIn)
    82  	rsSizeIn := metrics["response_size"]
    83  	assert.NotNil(rsSizeIn)
    84  	tcpRecIn := metrics["tcp_received"]
    85  	assert.NotNil(tcpRecIn)
    86  	tcpSentIn := metrics["tcp_sent"]
    87  	assert.NotNil(tcpSentIn)
    88  
    89  	assert.Equal(20.0, float64(grpcRecIn[0].Datapoints[0].Value))
    90  	assert.Equal(10.0, float64(grpcSentIn[0].Datapoints[0].Value))
    91  	assert.Equal(2.5, float64(rqCountIn[0].Datapoints[0].Value))
    92  	assert.Equal(4.5, float64(rqErrorCountIn[0].Datapoints[0].Value))
    93  	assert.Equal(1000.0, float64(rqThroughput[0].Datapoints[0].Value))
    94  	assert.Equal(1001.0, float64(rsThroughput[0].Datapoints[0].Value))
    95  	assertHisto(assert, rqSizeIn, "0.99", 0.7)
    96  	assertHisto(assert, rqDurationMillisIn, "0.99", 0.8)
    97  	assertHisto(assert, rsSizeIn, "0.99", 0.9)
    98  	assert.Equal(13.0, float64(tcpRecIn[0].Datapoints[0].Value))  // L4 Telemetry is backwards
    99  	assert.Equal(11.0, float64(tcpSentIn[0].Datapoints[0].Value)) // L4 Telemetry is backwards
   100  }
   101  
   102  func assertHisto(assert *assert.Assertions, metrics []models.Metric, stat string, expected float64) {
   103  	for _, m := range metrics {
   104  		if m.Stat == stat {
   105  			assert.Equal(expected, m.Datapoints[0].Value)
   106  			return
   107  		}
   108  	}
   109  	assert.Fail(fmt.Sprintf("Stat %s not found in %v", stat, metrics))
   110  }
   111  
   112  func assertEmptyHisto(assert *assert.Assertions, metrics []models.Metric, stat string) {
   113  	for _, m := range metrics {
   114  		if m.Stat == stat {
   115  			assert.Empty(m.Datapoints, fmt.Sprintf("Expected stat %s to be empty", stat))
   116  			return
   117  		}
   118  	}
   119  	assert.Fail(fmt.Sprintf("Stat %s not found in %v", stat, metrics))
   120  }
   121  
   122  func TestGetAppMetrics(t *testing.T) {
   123  	assert := assert.New(t)
   124  	srv, api, err := setupMocked()
   125  	if err != nil {
   126  		t.Error(err)
   127  		return
   128  	}
   129  	labels := `reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"`
   130  	api.MockRange("sum(rate(istio_requests_total{"+labels+"}[5m]))", 1.5)
   131  	api.MockRangeErr("sum(rate(istio_requests_total{"+labels+`,response_code=~"^0$|^[4-5]\\d\\d$"}[5m])) OR sum(rate(istio_requests_total{`+labels+`,grpc_response_status=~"^[1-9]$|^1[0-6]$",response_code!~"^0$|^[4-5]\\d\\d$"}[5m]))`, 3.5)
   132  	api.MockRange("sum(rate(istio_request_bytes_sum{"+labels+"}[5m]))", 1000)
   133  	api.MockRange("sum(rate(istio_response_bytes_sum{"+labels+"}[5m]))", 1001)
   134  	api.MockRange("sum(rate(istio_request_messages_total{"+labels+"}[5m]))", 10)
   135  	api.MockRange("sum(rate(istio_response_messages_total{"+labels+"}[5m]))", 20)
   136  	api.MockRange("sum(rate(istio_tcp_received_bytes_total{"+labels+"}[5m]))", 10)
   137  	api.MockRange("sum(rate(istio_tcp_sent_bytes_total{"+labels+"}[5m]))", 12)
   138  	api.MockRange("sum(rate(istio_tcp_connections_closed_total{"+labels+"}[5m]))", 31)
   139  	api.MockRange("sum(rate(istio_tcp_connections_opened_total{"+labels+"}[5m]))", 32)
   140  	api.MockHistoRange("istio_request_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.4)
   141  	api.MockHistoRange("istio_request_duration_seconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.5)
   142  	api.MockHistoRange("istio_request_duration_milliseconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.5)
   143  	api.MockHistoRange("istio_response_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.6)
   144  
   145  	q := models.IstioMetricsQuery{
   146  		Namespace: "bookinfo",
   147  		App:       "productpage",
   148  	}
   149  	q.FillDefaults()
   150  	q.RateInterval = "5m"
   151  	q.Quantiles = []string{"0.5", "0.95", "0.99"}
   152  	metrics, err := srv.GetMetrics(q, nil)
   153  
   154  	assert.Nil(err)
   155  	assert.Equal(13, len(metrics))
   156  	grpcRecIn := metrics["grpc_received"]
   157  	assert.NotNil(grpcRecIn)
   158  	grpcSentIn := metrics["grpc_sent"]
   159  	assert.NotNil(grpcSentIn)
   160  	rqCountIn := metrics["request_count"]
   161  	assert.NotNil(rqCountIn)
   162  	rqErrorCountIn := metrics["request_error_count"]
   163  	assert.NotNil(rqErrorCountIn)
   164  	rqThroughput := metrics["request_throughput"]
   165  	assert.NotNil(rqThroughput)
   166  	rsThroughput := metrics["response_throughput"]
   167  	assert.NotNil(rsThroughput)
   168  	rqSizeIn := metrics["request_size"]
   169  	assert.NotNil(rqSizeIn)
   170  	rqDurationMillisIn := metrics["request_duration_millis"]
   171  	assert.NotNil(rqDurationMillisIn)
   172  	rsSizeIn := metrics["response_size"]
   173  	assert.NotNil(rsSizeIn)
   174  	tcpRecIn := metrics["tcp_received"]
   175  	assert.NotNil(tcpRecIn)
   176  	tcpSentIn := metrics["tcp_sent"]
   177  	assert.NotNil(tcpSentIn)
   178  
   179  	assert.Equal(20.0, float64(grpcRecIn[0].Datapoints[0].Value))
   180  	assert.Equal(10.0, float64(grpcSentIn[0].Datapoints[0].Value))
   181  	assert.Equal(1.5, float64(rqCountIn[0].Datapoints[0].Value))
   182  	assert.Equal(3.5, float64(rqErrorCountIn[0].Datapoints[0].Value))
   183  	assert.Equal(1000.0, float64(rqThroughput[0].Datapoints[0].Value))
   184  	assert.Equal(1001.0, float64(rsThroughput[0].Datapoints[0].Value))
   185  	assertHisto(assert, rqSizeIn, "avg", 0.35)
   186  	assertHisto(assert, rqSizeIn, "0.5", 0.2)
   187  	assertHisto(assert, rqSizeIn, "0.95", 0.3)
   188  	assertHisto(assert, rqSizeIn, "0.99", 0.4)
   189  	assertHisto(assert, rqDurationMillisIn, "0.99", 0.5)
   190  	assertHisto(assert, rsSizeIn, "0.99", 0.6)
   191  	assert.Equal(12.0, float64(tcpRecIn[0].Datapoints[0].Value))  // L4 Telemetry is backwards
   192  	assert.Equal(10.0, float64(tcpSentIn[0].Datapoints[0].Value)) // L4 Telemetry is backwards
   193  }
   194  
   195  func TestGetFilteredAppMetrics(t *testing.T) {
   196  	assert := assert.New(t)
   197  	srv, api, err := setupMocked()
   198  	if err != nil {
   199  		t.Error(err)
   200  		return
   201  	}
   202  	api.MockRange(`sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}[5m]))`, 1.5)
   203  	api.MockHistoRange("istio_request_bytes", `{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}[5m]`, 0.35, 0.2, 0.3, 0.4)
   204  	q := models.IstioMetricsQuery{
   205  		Namespace: "bookinfo",
   206  		App:       "productpage",
   207  	}
   208  	q.FillDefaults()
   209  	q.RateInterval = "5m"
   210  	q.Filters = []string{"request_count", "request_size"}
   211  	metrics, err := srv.GetMetrics(q, nil)
   212  
   213  	assert.Nil(err)
   214  	assert.Equal(2, len(metrics))
   215  	rqCountOut := metrics["request_count"]
   216  	assert.NotNil(rqCountOut)
   217  	rqSizeOut := metrics["request_size"]
   218  	assert.NotNil(rqSizeOut)
   219  }
   220  
   221  func TestGetAppMetricsInstantRates(t *testing.T) {
   222  	assert := assert.New(t)
   223  	srv, api, err := setupMocked()
   224  	if err != nil {
   225  		t.Error(err)
   226  		return
   227  	}
   228  	api.MockRange(`sum(irate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}[1m]))`, 1.5)
   229  	q := models.IstioMetricsQuery{
   230  		Namespace: "bookinfo",
   231  		App:       "productpage",
   232  	}
   233  	q.FillDefaults()
   234  	q.RateFunc = "irate"
   235  	q.Filters = []string{"request_count"}
   236  	metrics, err := srv.GetMetrics(q, nil)
   237  
   238  	assert.Nil(err)
   239  	assert.Equal(1, len(metrics))
   240  	rqCountOut := metrics["request_count"]
   241  	assert.NotNil(rqCountOut)
   242  }
   243  
   244  func TestGetAppMetricsUnavailable(t *testing.T) {
   245  	assert := assert.New(t)
   246  	srv, api, err := setupMocked()
   247  	if err != nil {
   248  		t.Error(err)
   249  		return
   250  	}
   251  	// Mock everything to return empty data
   252  	api.MockEmptyRange(`sum(rate(istio_requests_total{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}[5m]))`)
   253  	api.MockEmptyHistoRange("istio_request_bytes", `{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}[5m]`)
   254  	q := models.IstioMetricsQuery{
   255  		Namespace: "bookinfo",
   256  		App:       "productpage",
   257  	}
   258  	q.FillDefaults()
   259  	q.RateInterval = "5m"
   260  	q.Quantiles = []string{"0.5", "0.95", "0.99"}
   261  	q.Filters = []string{"request_count", "request_size"}
   262  	metrics, err := srv.GetMetrics(q, nil)
   263  
   264  	assert.Nil(err)
   265  	assert.Equal(2, len(metrics))
   266  	// Simple metric & histogram are empty
   267  	rqCountIn := metrics["request_count"]
   268  	assert.NotNil(rqCountIn)
   269  	assert.Empty(rqCountIn[0].Datapoints)
   270  
   271  	rqSizeIn := metrics["request_size"]
   272  	assert.NotNil(rqSizeIn)
   273  	assertEmptyHisto(assert, rqSizeIn, "avg")
   274  	assertEmptyHisto(assert, rqSizeIn, "0.5")
   275  	assertEmptyHisto(assert, rqSizeIn, "0.95")
   276  	assertEmptyHisto(assert, rqSizeIn, "0.99")
   277  }
   278  
   279  func TestGetNamespaceMetrics(t *testing.T) {
   280  	assert := assert.New(t)
   281  	srv, api, err := setupMocked()
   282  	if err != nil {
   283  		t.Error(err)
   284  		return
   285  	}
   286  	labels := `reporter="source",source_workload_namespace="bookinfo"`
   287  	api.MockRange("sum(rate(istio_requests_total{"+labels+"}[5m]))", 1.5)
   288  	api.MockRangeErr("sum(rate(istio_requests_total{"+labels+`,response_code=~"^0$|^[4-5]\\d\\d$"}[5m])) OR sum(rate(istio_requests_total{`+labels+`,grpc_response_status=~"^[1-9]$|^1[0-6]$",response_code!~"^0$|^[4-5]\\d\\d$"}[5m]))`, 3.5)
   289  	api.MockRange("sum(rate(istio_request_bytes_sum{"+labels+"}[5m]))", 1000)
   290  	api.MockRange("sum(rate(istio_response_bytes_sum{"+labels+"}[5m]))", 1001)
   291  	api.MockRange("sum(rate(istio_request_messages_total{"+labels+"}[5m]))", 10)
   292  	api.MockRange("sum(rate(istio_response_messages_total{"+labels+"}[5m]))", 20)
   293  	api.MockRange("sum(rate(istio_tcp_received_bytes_total{"+labels+"}[5m]))", 10)
   294  	api.MockRange("sum(rate(istio_tcp_sent_bytes_total{"+labels+"}[5m]))", 12)
   295  	api.MockRange("sum(rate(istio_tcp_connections_closed_total{"+labels+"}[5m]))", 31)
   296  	api.MockRange("sum(rate(istio_tcp_connections_opened_total{"+labels+"}[5m]))", 32)
   297  	api.MockHistoRange("istio_request_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.4)
   298  	api.MockHistoRange("istio_request_duration_seconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.5)
   299  	api.MockHistoRange("istio_request_duration_milliseconds", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.5)
   300  	api.MockHistoRange("istio_response_bytes", "{"+labels+"}[5m]", 0.35, 0.2, 0.3, 0.6)
   301  
   302  	q := models.IstioMetricsQuery{
   303  		Namespace: "bookinfo",
   304  	}
   305  	q.FillDefaults()
   306  	q.RateInterval = "5m"
   307  	q.Quantiles = []string{"0.5", "0.95", "0.99"}
   308  	metrics, err := srv.GetMetrics(q, nil)
   309  
   310  	assert.Nil(err)
   311  	assert.Equal(13, len(metrics))
   312  	grpcRecOut := metrics["grpc_received"]
   313  	assert.NotNil(grpcRecOut)
   314  	grpcSentOut := metrics["grpc_sent"]
   315  	assert.NotNil(grpcSentOut)
   316  	rqCountOut := metrics["request_count"]
   317  	assert.NotNil(rqCountOut)
   318  	rqErrorCountOut := metrics["request_error_count"]
   319  	assert.NotNil(rqErrorCountOut)
   320  	rqThroughput := metrics["request_throughput"]
   321  	assert.NotNil(rqThroughput)
   322  	rsThroughput := metrics["response_throughput"]
   323  	assert.NotNil(rsThroughput)
   324  	rqSizeOut := metrics["request_size"]
   325  	assert.NotNil(rqSizeOut)
   326  	rqDurationMillisOut := metrics["request_duration_millis"]
   327  	assert.NotNil(rqDurationMillisOut)
   328  	rsSizeOut := metrics["response_size"]
   329  	assert.NotNil(rsSizeOut)
   330  	tcpRecOut := metrics["tcp_received"]
   331  	assert.NotNil(tcpRecOut)
   332  	tcpSentOut := metrics["tcp_sent"]
   333  	assert.NotNil(tcpSentOut)
   334  
   335  	assert.Equal(20.0, float64(grpcRecOut[0].Datapoints[0].Value))
   336  	assert.Equal(10.0, float64(grpcSentOut[0].Datapoints[0].Value))
   337  	assert.Equal(1.5, float64(rqCountOut[0].Datapoints[0].Value))
   338  	assert.Equal(3.5, float64(rqErrorCountOut[0].Datapoints[0].Value))
   339  	assert.Equal(1000.0, float64(rqThroughput[0].Datapoints[0].Value))
   340  	assert.Equal(1001.0, float64(rsThroughput[0].Datapoints[0].Value))
   341  	assertHisto(assert, rqSizeOut, "avg", 0.35)
   342  	assertHisto(assert, rqSizeOut, "0.5", 0.2)
   343  	assertHisto(assert, rqSizeOut, "0.95", 0.3)
   344  	assertHisto(assert, rqSizeOut, "0.99", 0.4)
   345  	assertHisto(assert, rqDurationMillisOut, "0.99", 0.5)
   346  	assertHisto(assert, rsSizeOut, "0.99", 0.6)
   347  	assert.Equal(12.0, float64(tcpRecOut[0].Datapoints[0].Value))  // L4 Telemetry is backwards
   348  	assert.Equal(10.0, float64(tcpSentOut[0].Datapoints[0].Value)) // L4 Telemetry is backwards
   349  }
   350  
   351  func TestCreateMetricsLabelsBuilder(t *testing.T) {
   352  	assert := assert.New(t)
   353  	q := models.IstioMetricsQuery{
   354  		Namespace: "bookinfo",
   355  		App:       "productpage",
   356  	}
   357  	q.FillDefaults()
   358  	q.Reporter = "source"
   359  	lb := createMetricsLabelsBuilder(&q)
   360  	assert.Equal(`{reporter="source",source_workload_namespace="bookinfo",source_canonical_service="productpage"}`, lb.Build())
   361  }
   362  
   363  func TestCreateStatsMetricsLabelsBuilder(t *testing.T) {
   364  	assert := assert.New(t)
   365  	q := models.MetricsStatsQuery{
   366  		Target: models.Target{
   367  			Namespace: "ns3",
   368  			Name:      "foo",
   369  			Kind:      "app",
   370  		},
   371  		Direction: "inbound",
   372  		Interval:  "3h",
   373  		Avg:       true,
   374  		Quantiles: []string{"0.90", "0.5"},
   375  		QueryTime: time.Now(),
   376  	}
   377  	lb := createStatsMetricsLabelsBuilder(&q)
   378  	assert.Equal(`{reporter="destination",destination_workload_namespace="ns3",destination_canonical_service="foo"}`, lb.Build())
   379  }
   380  
   381  func TestCreateStatsMetricsLabelsBuilderWithPeer(t *testing.T) {
   382  	assert := assert.New(t)
   383  	q := models.MetricsStatsQuery{
   384  		Target: models.Target{
   385  			Namespace: "ns3",
   386  			Name:      "foo",
   387  			Kind:      "app",
   388  		},
   389  		PeerTarget: &models.Target{
   390  			Namespace: "ns4",
   391  			Name:      "bar",
   392  			Kind:      "app",
   393  		},
   394  		Direction: "inbound",
   395  		Interval:  "3h",
   396  		Avg:       true,
   397  		Quantiles: []string{"0.90", "0.5"},
   398  		QueryTime: time.Now(),
   399  	}
   400  	lb := createStatsMetricsLabelsBuilder(&q)
   401  	assert.Equal(`{reporter="destination",destination_workload_namespace="ns3",destination_canonical_service="foo",source_workload_namespace="ns4",source_canonical_service="bar"}`, lb.Build())
   402  }
   403  
   404  func TestGetMetricsStats(t *testing.T) {
   405  	assert := assert.New(t)
   406  	srv, api, err := setupMocked()
   407  	if err != nil {
   408  		t.Error(err)
   409  		return
   410  	}
   411  
   412  	queryTime := time.Now()
   413  	queries := []models.MetricsStatsQuery{{
   414  		Target: models.Target{
   415  			Namespace: "ns1",
   416  			Name:      "foo",
   417  			Kind:      "app",
   418  		},
   419  		Direction:    "outbound",
   420  		Interval:     "30m",
   421  		RawInterval:  "30m",
   422  		Avg:          true,
   423  		Quantiles:    []string{"0.95"},
   424  		RawQueryTime: queryTime.Unix(),
   425  	}, {
   426  		Target: models.Target{
   427  			Namespace: "ns2",
   428  			Name:      "bar",
   429  			Kind:      "service",
   430  		},
   431  		PeerTarget: &models.Target{
   432  			Namespace: "ns3",
   433  			Name:      "w1",
   434  			Kind:      "workload",
   435  		},
   436  		Direction:    "inbound",
   437  		Interval:     "3h",
   438  		RawInterval:  "3h",
   439  		Avg:          false,
   440  		Quantiles:    []string{"0.5", "0.95"},
   441  		RawQueryTime: queryTime.Unix(),
   442  	}}
   443  
   444  	// Setup mocks
   445  	v0 := model.Vector{createSample(0)}
   446  	q1Avg := model.Vector{createSample(5)}
   447  	q1P95 := model.Vector{createSample(8)}
   448  	q2P50 := model.Vector{createSample(6.3)}
   449  	q2P95 := model.Vector{createSample(9.3)}
   450  	q1Labels := `reporter="source",source_workload_namespace="ns1",source_canonical_service="foo"`
   451  	q2Labels := `reporter="destination",destination_service_name="bar",destination_service_namespace="ns2",source_workload_namespace="ns3",source_workload="w1"`
   452  	api.MockHistoValue("istio_request_duration_milliseconds", "{"+q1Labels+"}[30m]", q1Avg, v0, q1P95, v0)
   453  	api.MockHistoValue("istio_request_duration_milliseconds", "{"+q2Labels+"}[3h]", v0, q2P50, q2P95, v0)
   454  
   455  	stats, err := srv.GetStats(queries)
   456  
   457  	assert.Nil(err)
   458  	assert.Len(stats, 2)
   459  	fmt.Printf("%v\n", stats)
   460  	assert.Equal([]models.Stat{{Name: "0.95", Value: 8.0}, {Name: "avg", Value: 5.0}}, stats["ns1:app:foo::outbound:30m"].ResponseTimes)
   461  	assert.Equal([]models.Stat{{Name: "0.5", Value: 6.3}, {Name: "0.95", Value: 9.3}}, stats["ns2:service:bar:ns3:workload:w1:inbound:3h"].ResponseTimes)
   462  }
   463  
   464  func createSample(value float64) *model.Sample {
   465  	return &model.Sample{
   466  		Timestamp: model.Now(),
   467  		Value:     model.SampleValue(value),
   468  		Metric:    model.Metric{},
   469  	}
   470  }