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 }