github.com/kiali/kiali@v1.84.0/graph/telemetry/istio/appender/throughput_test.go (about) 1 package appender 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/prometheus/common/model" 8 "github.com/stretchr/testify/assert" 9 10 "github.com/kiali/kiali/config" 11 "github.com/kiali/kiali/graph" 12 ) 13 14 func TestResponseThroughput(t *testing.T) { 15 assert := assert.New(t) 16 17 q0 := `round(sum(rate(istio_response_bytes_sum{reporter="destination",source_workload_namespace!="bookinfo",destination_service_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 18 q0m0 := model.Metric{ 19 "source_cluster": config.DefaultClusterID, 20 "source_workload_namespace": "istio-system", 21 "source_workload": "ingressgateway-unknown", 22 "source_canonical_service": "ingressgateway", 23 "source_canonical_revision": model.LabelValue(graph.Unknown), 24 "destination_cluster": config.DefaultClusterID, 25 "destination_service_namespace": "bookinfo", 26 "destination_service": "productpage.bookinfo.svc.cluster.local", 27 "destination_service_name": "productpage", 28 "destination_workload_namespace": "bookinfo", 29 "destination_workload": "productpage-v1", 30 "destination_canonical_service": "productpage", 31 "destination_canonical_revision": "v1"} 32 v0 := model.Vector{ 33 &model.Sample{ 34 Metric: q0m0, 35 Value: 1000.0}, 36 } 37 38 q1 := `round(sum(rate(istio_response_bytes_sum{reporter="destination",source_workload_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 39 q1m0 := model.Metric{ 40 "source_cluster": config.DefaultClusterID, 41 "source_workload_namespace": "bookinfo", 42 "source_workload": "productpage-v1", 43 "source_canonical_service": "productpage", 44 "source_canonical_revision": "v1", 45 "destination_cluster": config.DefaultClusterID, 46 "destination_service_namespace": "bookinfo", 47 "destination_service": "reviews.bookinfo.svc.cluster.local", 48 "destination_service_name": "reviews", 49 "destination_workload_namespace": "bookinfo", 50 "destination_workload": "reviews-v1", 51 "destination_canonical_service": "reviews", 52 "destination_canonical_revision": "v1"} 53 q1m1 := model.Metric{ 54 "source_cluster": config.DefaultClusterID, 55 "source_workload_namespace": "bookinfo", 56 "source_workload": "productpage-v1", 57 "source_canonical_service": "productpage", 58 "source_canonical_revision": "v1", 59 "destination_cluster": config.DefaultClusterID, 60 "destination_service_namespace": "bookinfo", 61 "destination_service": "reviews.bookinfo.svc.cluster.local", 62 "destination_service_name": "reviews", 63 "destination_workload_namespace": "bookinfo", 64 "destination_workload": "reviews-v2", 65 "destination_canonical_service": "reviews", 66 "destination_canonical_revision": "v2"} 67 q1m2 := model.Metric{ 68 "source_cluster": config.DefaultClusterID, 69 "source_workload_namespace": "bookinfo", 70 "source_workload": "reviews-v1", 71 "source_canonical_service": "reviews", 72 "source_canonical_revision": "v1", 73 "destination_cluster": config.DefaultClusterID, 74 "destination_service_namespace": "bookinfo", 75 "destination_service": "ratings.bookinfo.svc.cluster.local", 76 "destination_service_name": "ratings", 77 "destination_workload_namespace": "bookinfo", 78 "destination_workload": "ratings-v1", 79 "destination_canonical_service": "ratings", 80 "destination_canonical_revision": "v1"} 81 q1m3 := model.Metric{ 82 "source_cluster": config.DefaultClusterID, 83 "source_workload_namespace": "bookinfo", 84 "source_workload": "reviews-v2", 85 "source_canonical_service": "reviews", 86 "source_canonical_revision": "v2", 87 "destination_cluster": config.DefaultClusterID, 88 "destination_service_namespace": "bookinfo", 89 "destination_service": "ratings.bookinfo.svc.cluster.local", 90 "destination_service_name": "ratings", 91 "destination_workload_namespace": "bookinfo", 92 "destination_workload": "ratings-v1", 93 "destination_canonical_service": "ratings", 94 "destination_canonical_revision": "v1"} 95 v1 := model.Vector{ 96 &model.Sample{ 97 Metric: q1m0, 98 Value: 1000.0}, 99 &model.Sample{ 100 Metric: q1m1, 101 Value: 2000.0}, 102 &model.Sample{ 103 Metric: q1m2, 104 Value: 1000.0}, 105 &model.Sample{ 106 Metric: q1m3, 107 Value: 2000.0}} 108 109 client, api, err := setupMocked() 110 if err != nil { 111 t.Error(err) 112 return 113 } 114 mockQuery(api, q0, &v0) 115 mockQuery(api, q1, &v1) 116 117 trafficMap := responseThroughputTestTraffic() 118 ingressID, _, _ := graph.Id(config.DefaultClusterID, "istio-system", "", "istio-system", "ingressgateway-unknown", "ingressgateway", graph.Unknown, graph.GraphTypeVersionedApp) 119 ingress, ok := trafficMap[ingressID] 120 assert.Equal(true, ok) 121 assert.Equal("ingressgateway", ingress.App) 122 assert.Equal(1, len(ingress.Edges)) 123 assert.Equal(nil, ingress.Edges[0].Metadata[graph.Throughput]) 124 125 duration, _ := time.ParseDuration("60s") 126 appender := ThroughputAppender{ 127 GraphType: graph.GraphTypeVersionedApp, 128 InjectServiceNodes: true, 129 Namespaces: map[string]graph.NamespaceInfo{ 130 "bookinfo": { 131 Name: "bookinfo", 132 Duration: duration, 133 }, 134 }, 135 QueryTime: time.Now().Unix(), 136 Rates: graph.RequestedRates{ 137 Grpc: graph.RateRequests, 138 Http: graph.RateRequests, 139 Tcp: graph.RateTotal, 140 }, 141 ThroughputType: "response", 142 } 143 144 appender.appendGraph(trafficMap, "bookinfo", client) 145 146 ingress, ok = trafficMap[ingressID] 147 assert.Equal(true, ok) 148 assert.Equal("ingressgateway", ingress.App) 149 assert.Equal(1, len(ingress.Edges)) 150 _, ok = ingress.Edges[0].Metadata[graph.Throughput] 151 assert.Equal(false, ok) 152 153 productpageService := ingress.Edges[0].Dest 154 assert.Equal(graph.NodeTypeService, productpageService.NodeType) 155 assert.Equal("productpage", productpageService.Service) 156 assert.Equal(nil, productpageService.Metadata[graph.Throughput]) 157 assert.Equal(1, len(productpageService.Edges)) 158 assert.Equal(1000.0, productpageService.Edges[0].Metadata[graph.Throughput]) 159 160 productpage := productpageService.Edges[0].Dest 161 assert.Equal("productpage", productpage.App) 162 assert.Equal("v1", productpage.Version) 163 assert.Equal(nil, productpage.Metadata[graph.Throughput]) 164 assert.Equal(1, len(productpage.Edges)) 165 _, ok = productpage.Edges[0].Metadata[graph.Throughput] 166 assert.Equal(false, ok) 167 168 reviewsService := productpage.Edges[0].Dest 169 assert.Equal(graph.NodeTypeService, reviewsService.NodeType) 170 assert.Equal("reviews", reviewsService.Service) 171 assert.Equal(nil, reviewsService.Metadata[graph.Throughput]) 172 assert.Equal(2, len(reviewsService.Edges)) 173 assert.Equal(1000.0, reviewsService.Edges[0].Metadata[graph.Throughput]) 174 assert.Equal(2000.0, reviewsService.Edges[1].Metadata[graph.Throughput]) 175 176 reviews1 := reviewsService.Edges[0].Dest 177 assert.Equal("reviews", reviews1.App) 178 assert.Equal("v1", reviews1.Version) 179 assert.Equal(nil, reviews1.Metadata[graph.Throughput]) 180 assert.Equal(1, len(reviews1.Edges)) 181 _, ok = reviews1.Edges[0].Metadata[graph.Throughput] 182 assert.Equal(false, ok) 183 184 reviews2 := reviewsService.Edges[1].Dest 185 assert.Equal("reviews", reviews2.App) 186 assert.Equal("v2", reviews2.Version) 187 assert.Equal(nil, reviews2.Metadata[graph.Throughput]) 188 assert.Equal(1, len(reviews2.Edges)) 189 _, ok = reviews2.Edges[0].Metadata[graph.Throughput] 190 assert.False(ok) 191 192 ratingsService := reviews1.Edges[0].Dest 193 assert.Equal(graph.NodeTypeService, ratingsService.NodeType) 194 assert.Equal("ratings", ratingsService.Service) 195 assert.Equal(nil, ratingsService.Metadata[graph.Throughput]) 196 assert.Equal(1, len(ratingsService.Edges)) 197 assert.Equal(3000.0, ratingsService.Edges[0].Metadata[graph.Throughput]) 198 199 assert.Equal(ratingsService, reviews2.Edges[0].Dest) 200 201 ratings := ratingsService.Edges[0].Dest 202 assert.Equal("ratings", ratings.App) 203 assert.Equal("v1", ratings.Version) 204 assert.Equal(nil, ratings.Metadata[graph.Throughput]) 205 assert.Equal(0, len(ratings.Edges)) 206 } 207 208 func responseThroughputTestTraffic() graph.TrafficMap { 209 ingress, _ := graph.NewNode(config.DefaultClusterID, "istio-system", "", "istio-system", "ingressgateway-unknown", "ingressgateway", graph.Unknown, graph.GraphTypeVersionedApp) 210 productpageService, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "productpage", "", "", "", "", graph.GraphTypeVersionedApp) 211 productpage, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "productpage", "bookinfo", "productpage-v1", "productpage", "v1", graph.GraphTypeVersionedApp) 212 reviewsService, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "reviews", "", "", "", "", graph.GraphTypeVersionedApp) 213 reviewsV1, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "reviews", "bookinfo", "reviews-v1", "reviews", "v1", graph.GraphTypeVersionedApp) 214 reviewsV2, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "reviews", "bookinfo", "reviews-v2", "reviews", "v2", graph.GraphTypeVersionedApp) 215 ratingsService, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "ratings", "", "", "", "", graph.GraphTypeVersionedApp) 216 ratings, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "ratings", "bookinfo", "ratings-v1", "ratings", "v1", graph.GraphTypeVersionedApp) 217 trafficMap := graph.NewTrafficMap() 218 219 trafficMap[ingress.ID] = ingress 220 trafficMap[productpageService.ID] = productpageService 221 trafficMap[productpage.ID] = productpage 222 trafficMap[reviewsService.ID] = reviewsService 223 trafficMap[reviewsV1.ID] = reviewsV1 224 trafficMap[reviewsV2.ID] = reviewsV2 225 trafficMap[ratingsService.ID] = ratingsService 226 trafficMap[ratings.ID] = ratings 227 228 ingress.AddEdge(productpageService).Metadata[graph.ProtocolKey] = "http" 229 productpageService.AddEdge(productpage).Metadata[graph.ProtocolKey] = "http" 230 productpage.AddEdge(reviewsService).Metadata[graph.ProtocolKey] = "http" 231 reviewsService.AddEdge(reviewsV1).Metadata[graph.ProtocolKey] = "http" 232 reviewsService.AddEdge(reviewsV2).Metadata[graph.ProtocolKey] = "http" 233 reviewsV1.AddEdge(ratingsService).Metadata[graph.ProtocolKey] = "http" 234 reviewsV2.AddEdge(ratingsService).Metadata[graph.ProtocolKey] = "http" 235 ratingsService.AddEdge(ratings).Metadata[graph.ProtocolKey] = "http" 236 237 return trafficMap 238 } 239 240 func TestRequestThroughput(t *testing.T) { 241 assert := assert.New(t) 242 243 q0 := `round(sum(rate(istio_request_bytes_sum{reporter="source",source_workload_namespace!="bookinfo",destination_service_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 244 q0m0 := model.Metric{ 245 "source_cluster": config.DefaultClusterID, 246 "source_workload_namespace": "istio-system", 247 "source_workload": "ingressgateway-unknown", 248 "source_canonical_service": "ingressgateway", 249 "source_canonical_revision": model.LabelValue(graph.Unknown), 250 "destination_cluster": config.DefaultClusterID, 251 "destination_service_namespace": "bookinfo", 252 "destination_service": "productpage.bookinfo.svc.cluster.local", 253 "destination_service_name": "productpage", 254 "destination_workload_namespace": "bookinfo", 255 "destination_workload": "productpage-v1", 256 "destination_canonical_service": "productpage", 257 "destination_canonical_revision": "v1"} 258 v0 := model.Vector{ 259 &model.Sample{ 260 Metric: q0m0, 261 Value: 1000.0}, 262 } 263 264 q1 := `round(sum(rate(istio_request_bytes_sum{reporter="source",source_workload_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 265 q1m0 := model.Metric{ 266 "source_cluster": config.DefaultClusterID, 267 "source_workload_namespace": "bookinfo", 268 "source_workload": "productpage-v1", 269 "source_canonical_service": "productpage", 270 "source_canonical_revision": "v1", 271 "destination_cluster": config.DefaultClusterID, 272 "destination_service_namespace": "bookinfo", 273 "destination_service": "reviews.bookinfo.svc.cluster.local", 274 "destination_service_name": "reviews", 275 "destination_workload_namespace": "unknown", // simulate failed requests to reviews service 276 "destination_workload": "unknown", 277 "destination_canonical_service": "unknown", 278 "destination_canonical_revision": "unknown"} 279 v1 := model.Vector{ 280 &model.Sample{ 281 Metric: q1m0, 282 Value: 1000.0}} 283 284 client, api, err := setupMocked() 285 if err != nil { 286 t.Error(err) 287 return 288 } 289 mockQuery(api, q0, &v0) 290 mockQuery(api, q1, &v1) 291 292 trafficMap := requestThroughputTestTraffic() 293 ingressID, _, _ := graph.Id(config.DefaultClusterID, "istio-system", "", "istio-system", "ingressgateway-unknown", "ingressgateway", graph.Unknown, graph.GraphTypeVersionedApp) 294 ingress, ok := trafficMap[ingressID] 295 assert.Equal(true, ok) 296 assert.Equal("ingressgateway", ingress.App) 297 assert.Equal(1, len(ingress.Edges)) 298 assert.Equal(nil, ingress.Edges[0].Metadata[graph.Throughput]) 299 300 duration, _ := time.ParseDuration("60s") 301 appender := ThroughputAppender{ 302 GraphType: graph.GraphTypeVersionedApp, 303 InjectServiceNodes: true, 304 Namespaces: map[string]graph.NamespaceInfo{ 305 "bookinfo": { 306 Name: "bookinfo", 307 Duration: duration, 308 }, 309 }, 310 QueryTime: time.Now().Unix(), 311 Rates: graph.RequestedRates{ 312 Grpc: graph.RateRequests, 313 Http: graph.RateRequests, 314 Tcp: graph.RateTotal, 315 }, 316 ThroughputType: "request", 317 } 318 319 appender.appendGraph(trafficMap, "bookinfo", client) 320 321 ingress, ok = trafficMap[ingressID] 322 assert.Equal(true, ok) 323 assert.Equal("ingressgateway", ingress.App) 324 assert.Equal(1, len(ingress.Edges)) 325 _, ok = ingress.Edges[0].Metadata[graph.Throughput] 326 assert.Equal(false, ok) 327 328 productpageService := ingress.Edges[0].Dest 329 assert.Equal(graph.NodeTypeService, productpageService.NodeType) 330 assert.Equal("productpage", productpageService.Service) 331 assert.Equal(nil, productpageService.Metadata[graph.Throughput]) 332 assert.Equal(1, len(productpageService.Edges)) 333 assert.Equal(1000.0, productpageService.Edges[0].Metadata[graph.Throughput]) 334 335 productpage := productpageService.Edges[0].Dest 336 assert.Equal("productpage", productpage.App) 337 assert.Equal("v1", productpage.Version) 338 assert.Equal(nil, productpage.Metadata[graph.Throughput]) 339 assert.Equal(1, len(productpage.Edges)) 340 assert.Equal(1000.0, productpage.Edges[0].Metadata[graph.Throughput]) 341 342 reviewsService := productpage.Edges[0].Dest 343 assert.Equal(graph.NodeTypeService, reviewsService.NodeType) 344 assert.Equal("reviews", reviewsService.Service) 345 assert.Equal(nil, reviewsService.Metadata[graph.Throughput]) 346 assert.Equal(0, len(reviewsService.Edges)) 347 } 348 349 func TestRequestThroughputSkipRates(t *testing.T) { 350 assert := assert.New(t) 351 352 q0 := `round(sum(rate(istio_request_bytes_sum{reporter="source",source_workload_namespace!="bookinfo",destination_service_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 353 q0m0 := model.Metric{ 354 "source_cluster": config.DefaultClusterID, 355 "source_workload_namespace": "istio-system", 356 "source_workload": "ingressgateway-unknown", 357 "source_canonical_service": "ingressgateway", 358 "source_canonical_revision": model.LabelValue(graph.Unknown), 359 "destination_cluster": config.DefaultClusterID, 360 "destination_service_namespace": "bookinfo", 361 "destination_service": "productpage.bookinfo.svc.cluster.local", 362 "destination_service_name": "productpage", 363 "destination_workload_namespace": "bookinfo", 364 "destination_workload": "productpage-v1", 365 "destination_canonical_service": "productpage", 366 "destination_canonical_revision": "v1"} 367 v0 := model.Vector{ 368 &model.Sample{ 369 Metric: q0m0, 370 Value: 1000.0}, 371 } 372 373 q1 := `round(sum(rate(istio_request_bytes_sum{reporter="source",source_workload_namespace="bookinfo"}[60s])) by (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) > 0,0.001)` 374 q1m0 := model.Metric{ 375 "source_cluster": config.DefaultClusterID, 376 "source_workload_namespace": "bookinfo", 377 "source_workload": "productpage-v1", 378 "source_canonical_service": "productpage", 379 "source_canonical_revision": "v1", 380 "destination_cluster": config.DefaultClusterID, 381 "destination_service_namespace": "bookinfo", 382 "destination_service": "reviews.bookinfo.svc.cluster.local", 383 "destination_service_name": "reviews", 384 "destination_workload_namespace": "unknown", // simulate failed requests to reviews service 385 "destination_workload": "unknown", 386 "destination_canonical_service": "unknown", 387 "destination_canonical_revision": "unknown"} 388 v1 := model.Vector{ 389 &model.Sample{ 390 Metric: q1m0, 391 Value: 1000.0}} 392 393 _, api, err := setupMocked() 394 if err != nil { 395 t.Error(err) 396 return 397 } 398 mockQuery(api, q0, &v0) 399 mockQuery(api, q1, &v1) 400 401 trafficMap := requestThroughputTestTraffic() 402 ingressID, _, _ := graph.Id(config.DefaultClusterID, "istio-system", "", "istio-system", "ingressgateway-unknown", "ingressgateway", graph.Unknown, graph.GraphTypeVersionedApp) 403 ingress, ok := trafficMap[ingressID] 404 assert.Equal(true, ok) 405 assert.Equal("ingressgateway", ingress.App) 406 assert.Equal(1, len(ingress.Edges)) 407 assert.Equal(nil, ingress.Edges[0].Metadata[graph.Throughput]) 408 409 duration, _ := time.ParseDuration("60s") 410 appender := ThroughputAppender{ 411 GraphType: graph.GraphTypeVersionedApp, 412 InjectServiceNodes: true, 413 Namespaces: map[string]graph.NamespaceInfo{ 414 "bookinfo": { 415 Name: "bookinfo", 416 Duration: duration, 417 }, 418 }, 419 QueryTime: time.Now().Unix(), 420 Rates: graph.RequestedRates{ 421 Grpc: graph.RateRequests, 422 Http: graph.RateNone, 423 Tcp: graph.RateTotal, 424 }, 425 ThroughputType: "request", 426 } 427 428 appender.AppendGraph(trafficMap, nil, nil) 429 430 ingress, ok = trafficMap[ingressID] 431 assert.Equal(true, ok) 432 assert.Equal("ingressgateway", ingress.App) 433 assert.Equal(1, len(ingress.Edges)) 434 _, ok = ingress.Edges[0].Metadata[graph.Throughput] 435 assert.Equal(false, ok) 436 437 productpageService := ingress.Edges[0].Dest 438 assert.Equal(graph.NodeTypeService, productpageService.NodeType) 439 assert.Equal("productpage", productpageService.Service) 440 assert.Equal(nil, productpageService.Metadata[graph.Throughput]) 441 assert.Equal(1, len(productpageService.Edges)) 442 assert.Equal(nil, productpageService.Edges[0].Metadata[graph.Throughput]) 443 444 productpage := productpageService.Edges[0].Dest 445 assert.Equal("productpage", productpage.App) 446 assert.Equal("v1", productpage.Version) 447 assert.Equal(nil, productpage.Metadata[graph.Throughput]) 448 assert.Equal(1, len(productpage.Edges)) 449 assert.Equal(nil, productpage.Edges[0].Metadata[graph.Throughput]) 450 451 reviewsService := productpage.Edges[0].Dest 452 assert.Equal(graph.NodeTypeService, reviewsService.NodeType) 453 assert.Equal("reviews", reviewsService.Service) 454 assert.Equal(nil, reviewsService.Metadata[graph.Throughput]) 455 assert.Equal(0, len(reviewsService.Edges)) 456 } 457 458 func requestThroughputTestTraffic() graph.TrafficMap { 459 ingress, _ := graph.NewNode(config.DefaultClusterID, "istio-system", "", "istio-system", "ingressgateway-unknown", "ingressgateway", graph.Unknown, graph.GraphTypeVersionedApp) 460 productpageService, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "productpage", "", "", "", "", graph.GraphTypeVersionedApp) 461 productpage, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "productpage", "bookinfo", "productpage-v1", "productpage", "v1", graph.GraphTypeVersionedApp) 462 reviewsService, _ := graph.NewNode(config.DefaultClusterID, "bookinfo", "reviews", "", "", "", "", graph.GraphTypeVersionedApp) 463 trafficMap := graph.NewTrafficMap() 464 465 trafficMap[ingress.ID] = ingress 466 trafficMap[productpageService.ID] = productpageService 467 trafficMap[productpage.ID] = productpage 468 trafficMap[reviewsService.ID] = reviewsService 469 470 ingress.AddEdge(productpageService).Metadata[graph.ProtocolKey] = "http" 471 productpageService.AddEdge(productpage).Metadata[graph.ProtocolKey] = "http" 472 productpage.AddEdge(reviewsService).Metadata[graph.ProtocolKey] = "http" 473 474 return trafficMap 475 }