├── .gitattributes ├── DaprSut.Server ├── appsettings.Development.json ├── appsettings.json ├── Properties │ └── launchSettings.json ├── PingPongActor.cs ├── Program.cs └── DaprSut.Server.csproj ├── OrleansSut.Server ├── appsettings.Development.json ├── PingPongGrain.cs ├── Properties │ └── launchSettings.json ├── appsettings.json ├── Program.cs └── OrleansSut.Server.csproj ├── TestRunner.Client ├── appsettings.Development.json ├── Properties │ └── launchSettings.json ├── appsettings.json ├── TestRunner.Client.csproj └── Program.cs ├── AkkaSut.Server ├── appsettings.Development.json ├── Properties │ └── launchSettings.json ├── PingPongActor.cs ├── appsettings.json ├── Program.cs ├── AkkaSut.Server.csproj └── AkkaClusterService.cs ├── ProtoActorSut.Server ├── appsettings.Development.json ├── Properties │ └── launchSettings.json ├── PingPongActor.cs ├── appsettings.json ├── PingPongActorRaw.cs ├── Program.cs └── ProtoActorSut.Server.csproj ├── kubernetes ├── values-dapr.yaml ├── dapr-app-config.yaml ├── dapr-state-store.yaml ├── values-seq.yaml ├── values-redis.yaml ├── values-prometheus.yaml ├── deployment-dapr-sut.yaml ├── deployment-test-runner-dapr.yaml ├── deployment-akka-sut.yaml ├── statefulset-lighthouse.yml ├── deployment-test-runner-akka.yaml ├── deployment-proto-actor-sut.yaml ├── deployment-test-runner-proto-actor.yaml ├── deployment-test-runner-proto-actor-raw.yaml ├── deployment-orleans-sut.yaml └── deployment-test-runner-orleans.yaml ├── AkkaSut.Shared ├── Consts.cs ├── Messages.cs ├── MessageExtractor.cs ├── AkkaSut.Shared.csproj └── ActorSystemExtensions.cs ├── ProtoActorSut.Shared ├── Consts.cs ├── protos.proto ├── ActorSystemHostedService.cs ├── ProtoActorSut.Shared.csproj └── ProtoActorExtensions.cs ├── TestRunner ├── Tests │ ├── TestServices.cs │ ├── TestManager.cs │ ├── ActivationTest.cs │ └── MessagingTest.cs ├── appsettings.Development.json ├── Dapr │ ├── DaprExtensions.cs │ └── DaprTestServices.cs ├── Orleans │ ├── OrleansExtensions.cs │ └── OrleansTestServices.cs ├── Akka │ ├── AkkaExtensions.cs │ ├── ClusterProxyHostedService.cs │ └── AkkaTestServices.cs ├── Monitoring │ └── TestMetrics.cs ├── ProtoActor │ ├── ProtoActorExtensions.cs │ ├── ProtoActorTestServices.cs │ └── ProtoActorTestServicesRaw.cs ├── appsettings.json ├── Bus │ ├── RunTestConsumer.cs │ └── MassTransitExtensions.cs ├── TestRunner.csproj ├── Properties │ └── launchSettings.json └── Program.cs ├── docker ├── image-akka-sut.sh ├── image-dapr-sut.sh ├── image-orleans-sut.sh ├── image-test-runner.sh ├── image-proto-actor-sut.sh ├── Dockerfile-TestRunner ├── Dockerfile-AkkaSut ├── Dockerfile-DaprSut ├── Dockerfile-OrleansSut └── Dockerfile-ProtoActorSut ├── TestRunner.Contract ├── Messages.cs └── TestRunner.Contract.csproj ├── OrleansSut.Shared ├── IPingPongGrain.cs ├── OrleansSut.Shared.csproj └── OrleansExtensions.cs ├── DaprSut.Shared ├── IPingPongActor.cs └── DaprSut.Shared.csproj ├── .dockerignore ├── azure ├── parameters.json └── template.json ├── docker-compose.yml ├── LICENSE ├── ActorBenchmark.sln ├── README.md └── .gitignore /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf -------------------------------------------------------------------------------- /DaprSut.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /OrleansSut.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /TestRunner.Client/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /AkkaSut.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /ProtoActorSut.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /kubernetes/values-dapr.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | prometheus: 3 | enabled: false -------------------------------------------------------------------------------- /AkkaSut.Shared/Consts.cs: -------------------------------------------------------------------------------- 1 | namespace AkkaSut.Shared; 2 | 3 | public static class Consts 4 | { 5 | public const string PingPongShardTypeName = "ping-pong"; 6 | } -------------------------------------------------------------------------------- /ProtoActorSut.Shared/Consts.cs: -------------------------------------------------------------------------------- 1 | namespace ProtoActorSut.Shared; 2 | 3 | public static class Consts 4 | { 5 | public const string PingPongRawKind = "PingPongRaw"; 6 | } -------------------------------------------------------------------------------- /AkkaSut.Shared/Messages.cs: -------------------------------------------------------------------------------- 1 | namespace AkkaSut.Shared; 2 | 3 | public record PingMessage(string ActorId, string Name); 4 | 5 | public record PongMessage(string Response); 6 | -------------------------------------------------------------------------------- /TestRunner/Tests/TestServices.cs: -------------------------------------------------------------------------------- 1 | namespace TestRunner.Tests; 2 | 3 | public delegate Task Ping(object handle, string name); 4 | 5 | public delegate Task Activate(string id); -------------------------------------------------------------------------------- /docker/image-akka-sut.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t abimgregistry.azurecr.io/akka-sut:latest -f Dockerfile-AkkaSut ../. 4 | docker push abimgregistry.azurecr.io/akka-sut:latest -------------------------------------------------------------------------------- /docker/image-dapr-sut.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t abimgregistry.azurecr.io/dapr-sut:latest -f Dockerfile-DaprSut ../. 4 | docker push abimgregistry.azurecr.io/dapr-sut:latest -------------------------------------------------------------------------------- /TestRunner/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docker/image-orleans-sut.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t abimgregistry.azurecr.io/orleans-sut:latest -f Dockerfile-OrleansSut ../. 4 | docker push abimgregistry.azurecr.io/orleans-sut:latest -------------------------------------------------------------------------------- /docker/image-test-runner.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t abimgregistry.azurecr.io/test-runner:latest -f Dockerfile-TestRunner ../. 4 | docker push abimgregistry.azurecr.io/test-runner:latest -------------------------------------------------------------------------------- /docker/image-proto-actor-sut.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | docker build -t abimgregistry.azurecr.io/proto-actor-sut:latest -f Dockerfile-ProtoActorSut ../. 4 | docker push abimgregistry.azurecr.io/proto-actor-sut:latest -------------------------------------------------------------------------------- /TestRunner.Contract/Messages.cs: -------------------------------------------------------------------------------- 1 | namespace TestRunner.Contract; 2 | 3 | public record RunMessagingTest(int Parallelism, int DurationInSeconds); 4 | 5 | public record RunActivationTest(int ActivationCount, int Parallelism); -------------------------------------------------------------------------------- /kubernetes/dapr-app-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Configuration 3 | metadata: 4 | name: actor-benchmark-config 5 | spec: 6 | tracing: 7 | samplingRate: "0" 8 | metric: 9 | enabled: false -------------------------------------------------------------------------------- /OrleansSut.Server/PingPongGrain.cs: -------------------------------------------------------------------------------- 1 | using Orleans; 2 | using OrleansSut.Shared; 3 | 4 | namespace OrleansSut.Server; 5 | 6 | public class PingPongGrain : Grain, IPingPongGrain 7 | { 8 | public ValueTask Ping(PingMessage ping) => 9 | ValueTask.FromResult(new PongMessage("Hello " + ping.Name)); 10 | } -------------------------------------------------------------------------------- /ProtoActorSut.Shared/protos.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "ProtoActorSut.Contracts"; 4 | 5 | message PingMessage { 6 | string name = 1; 7 | } 8 | 9 | message PongMessage { 10 | string response = 1; 11 | } 12 | 13 | service PingPongActor { 14 | rpc Ping(PingMessage) returns (PongMessage); 15 | } -------------------------------------------------------------------------------- /OrleansSut.Shared/IPingPongGrain.cs: -------------------------------------------------------------------------------- 1 | using Orleans; 2 | 3 | namespace OrleansSut.Shared; 4 | 5 | public interface IPingPongGrain : IGrainWithStringKey 6 | { 7 | ValueTask Ping(PingMessage ping); 8 | } 9 | 10 | public readonly record struct PingMessage(string Name); 11 | 12 | public readonly record struct PongMessage(string Response); -------------------------------------------------------------------------------- /TestRunner.Contract/TestRunner.Contract.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DaprSut.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SeqUrl": "http://localhost:5341", 3 | 4 | "Serilog": { 5 | "MinimumLevel": { 6 | "Default": "Information", 7 | "Override": { 8 | "Microsoft.AspNetCore": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | } 12 | }, 13 | 14 | "AllowedHosts": "*" 15 | } 16 | -------------------------------------------------------------------------------- /DaprSut.Shared/IPingPongActor.cs: -------------------------------------------------------------------------------- 1 | using Dapr.Actors; 2 | 3 | namespace DaprSut.Shared; 4 | 5 | public interface IPingPongActor : IActor 6 | { 7 | Task Ping(PingMessage ping); 8 | } 9 | 10 | public class PingMessage { 11 | public string Name { get; set; } 12 | 13 | }; 14 | 15 | public class PongMessage 16 | { 17 | public string Response { get; set; } 18 | }; -------------------------------------------------------------------------------- /ProtoActorSut.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ProtoActorSut.Server": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "http://localhost:5900", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AkkaSut.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AkkaSut.Server": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "https://localhost:7031;http://localhost:5031", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DaprSut.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DaprSut.Server": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "https://localhost:7071;http://localhost:5071", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DaprSut.Shared/DaprSut.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AkkaSut.Server/PingPongActor.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using AkkaSut.Shared; 3 | 4 | namespace AkkaSut.Server; 5 | 6 | public class PingPongActor : ReceiveActor 7 | { 8 | public PingPongActor() 9 | { 10 | ReceiveAsync(ping => 11 | { 12 | Sender.Tell(new PongMessage("Hello " + ping.Name)); 13 | return Task.CompletedTask; 14 | }); 15 | } 16 | } -------------------------------------------------------------------------------- /DaprSut.Server/PingPongActor.cs: -------------------------------------------------------------------------------- 1 | using Dapr.Actors.Runtime; 2 | using DaprSut.Shared; 3 | 4 | namespace DaprSut.Server; 5 | 6 | public class PingPongActor : Actor, IPingPongActor 7 | { 8 | public Task Ping(PingMessage ping) => 9 | Task.FromResult(new PongMessage {Response = "Hello " + ping.Name}); 10 | 11 | public PingPongActor(ActorHost host) : base(host) 12 | { 13 | } 14 | } -------------------------------------------------------------------------------- /OrleansSut.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "OrleansSut.Server": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "https://localhost:7187;http://localhost:5187", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ProtoActorSut.Server/PingPongActor.cs: -------------------------------------------------------------------------------- 1 | using Proto; 2 | using ProtoActorSut.Contracts; 3 | 4 | namespace ProtoActorSut.Server; 5 | 6 | public class PingPongActor : PingPongActorBase 7 | { 8 | public PingPongActor(IContext context) : base(context) { } 9 | 10 | public override Task Ping(PingMessage request) => 11 | Task.FromResult(new PongMessage {Response = "Hello " + request.Name}); 12 | } -------------------------------------------------------------------------------- /kubernetes/dapr-state-store.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: dapr-state-store 5 | labels: 6 | app: dapr-infrastructure 7 | spec: 8 | type: state.redis 9 | version: v1 10 | metadata: 11 | - name: redisHost 12 | value: my-redis-master.redis:6379 13 | - name: enableTLS 14 | value: "false" 15 | - name: actorStateStore 16 | value: "true" -------------------------------------------------------------------------------- /kubernetes/values-seq.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | limits: 3 | memory: 1Gi 4 | 5 | persistence: 6 | storageClass: managed 7 | size: 8Gi 8 | 9 | affinity: 10 | nodeAffinity: 11 | requiredDuringSchedulingIgnoredDuringExecution: 12 | nodeSelectorTerms: 13 | - matchExpressions: 14 | - key: test-role 15 | operator: In 16 | values: 17 | - management -------------------------------------------------------------------------------- /ProtoActorSut.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ProtoActor": { 3 | "ClusterName": "dev" 4 | }, 5 | 6 | "SeqUrl": "http://localhost:5341", 7 | 8 | "Serilog": { 9 | "MinimumLevel": { 10 | "Default": "Information", 11 | "Override": { 12 | "Microsoft.AspNetCore": "Warning", 13 | "Microsoft.Hosting.Lifetime": "Information" 14 | } 15 | } 16 | }, 17 | 18 | "AllowedHosts": "*" 19 | } 20 | -------------------------------------------------------------------------------- /kubernetes/values-redis.yaml: -------------------------------------------------------------------------------- 1 | architecture: standalone 2 | auth: 3 | enabled: false 4 | 5 | master: 6 | persistence: 7 | enabled: false 8 | affinity: 9 | nodeAffinity: 10 | requiredDuringSchedulingIgnoredDuringExecution: 11 | nodeSelectorTerms: 12 | - matchExpressions: 13 | - key: test-role 14 | operator: In 15 | values: 16 | - management -------------------------------------------------------------------------------- /TestRunner/Dapr/DaprExtensions.cs: -------------------------------------------------------------------------------- 1 | using TestRunner.Tests; 2 | 3 | namespace TestRunner.Dapr; 4 | 5 | 6 | public static class DaprExtensions 7 | { 8 | public static void AddDaprTestServices(this WebApplicationBuilder builder) 9 | { 10 | builder.Services.AddSingleton(DaprTestServices.Ping); 11 | builder.Services.AddSingleton(DaprTestServices.Activate); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AkkaSut.Shared/MessageExtractor.cs: -------------------------------------------------------------------------------- 1 | using Akka.Cluster.Sharding; 2 | 3 | namespace AkkaSut.Shared; 4 | 5 | public class MessageExtractor : HashCodeMessageExtractor 6 | { 7 | public MessageExtractor(int maxNumberOfShards) : base(maxNumberOfShards) { } 8 | 9 | public override string? EntityId(object message) => 10 | message switch 11 | { 12 | PingMessage ping => ping.ActorId, 13 | _ => null 14 | }; 15 | } -------------------------------------------------------------------------------- /TestRunner.Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "TestRunner.Client": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "launchUrl": "swagger", 9 | "applicationUrl": "http://localhost:5051", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AkkaSut.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Akka": { 3 | "ActorSystemName": "actor-benchmark", 4 | "Role": "sut", 5 | "PublicHostname": "localhost", 6 | "Port": 5055, 7 | "SeedNodes": ["localhost:5800"] 8 | }, 9 | 10 | "SeqUrl": "http://localhost:5341", 11 | 12 | "Serilog": { 13 | "MinimumLevel": { 14 | "Default": "Information", 15 | "Override": { 16 | "Microsoft.AspNetCore": "Warning", 17 | "Microsoft.Hosting.Lifetime": "Information" 18 | } 19 | } 20 | }, 21 | 22 | "AllowedHosts": "*" 23 | } 24 | -------------------------------------------------------------------------------- /TestRunner.Client/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Bus": { 3 | "RabbitMq": { 4 | "Host": "localhost", 5 | "User": "guest", 6 | "Password": "guest" 7 | }, 8 | 9 | "ServiceBusConnectionString": "_service_bus_connection_string_" 10 | }, 11 | 12 | "SeqUrl": "http://localhost:5341", 13 | 14 | "Serilog": { 15 | "MinimumLevel": { 16 | "Default": "Information", 17 | "Override": { 18 | "Microsoft.AspNetCore": "Warning", 19 | "Microsoft.Hosting.Lifetime": "Information" 20 | } 21 | } 22 | }, 23 | 24 | "AllowedHosts": "*" 25 | } 26 | -------------------------------------------------------------------------------- /OrleansSut.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Orleans": { 3 | "ClusterId": "dev", 4 | "ServiceId": "ActorBenchmark", 5 | "SiloPort": 11111, 6 | "GatewayPort": 30000, 7 | "DashboardPort": 8001, 8 | "ClusteringStorage": "_storage_connection_string_" 9 | }, 10 | 11 | "SeqUrl": "http://localhost:5341", 12 | 13 | "Serilog": { 14 | "MinimumLevel": { 15 | "Default": "Information", 16 | "Override": { 17 | "Microsoft.AspNetCore": "Warning", 18 | "Microsoft.Hosting.Lifetime": "Information" 19 | } 20 | } 21 | }, 22 | 23 | "AllowedHosts": "*" 24 | } 25 | -------------------------------------------------------------------------------- /TestRunner/Orleans/OrleansExtensions.cs: -------------------------------------------------------------------------------- 1 | using TestRunner.Tests; 2 | 3 | namespace TestRunner.Orleans; 4 | 5 | public static class OrleansExtensions 6 | { 7 | public static WebApplicationBuilder AddOrleansTestServices(this WebApplicationBuilder builder) 8 | { 9 | builder.Services.AddSingleton(); 10 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Ping); 11 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Activate); 12 | 13 | return builder; 14 | } 15 | } -------------------------------------------------------------------------------- /ProtoActorSut.Server/PingPongActorRaw.cs: -------------------------------------------------------------------------------- 1 | using Proto; 2 | using ProtoActorSut.Contracts; 3 | 4 | namespace ProtoActorSut.Server; 5 | 6 | public class PingPongActorRaw : IActor 7 | { 8 | private Task Ping(PingMessage request, IContext ctx) 9 | { 10 | ctx.Respond(new PongMessage {Response = "Hello " + request.Name}); 11 | return Task.CompletedTask; 12 | } 13 | 14 | public Task ReceiveAsync(IContext context) 15 | { 16 | if (context.Message is PingMessage ping) 17 | { 18 | return Ping(ping, context); 19 | } 20 | 21 | return Task.CompletedTask; 22 | } 23 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | **/.git 3 | **/.gitattributes 4 | **/.gitignore 5 | 6 | # GitLab 7 | **/.gitlab-ci.yml 8 | 9 | # JetBrains 10 | **/.idea/ 11 | **/*DotSettings* 12 | 13 | # Node 14 | **/logs 15 | **/*.log 16 | **/npm-debug.log* 17 | **/yarn-debug.log* 18 | **/yarn-error.log* 19 | **/node_modules/ 20 | 21 | # Visual Studio Code 22 | **/.vs 23 | **/.vscode 24 | **/*.code-workspace 25 | 26 | # .Net 27 | **/bin/ 28 | **/obj/ 29 | **/wwwroot/ 30 | **/*.trx 31 | 32 | # Docker 33 | **/Dockerfile* 34 | **/docker-compose* 35 | 36 | # macOS 37 | **/*.DS_Store 38 | **/.AppleDouble 39 | **/.LSOverride 40 | **/._* 41 | 42 | # Others 43 | **/out/ 44 | **/*.md -------------------------------------------------------------------------------- /AkkaSut.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using AkkaSut.Server; 3 | using AkkaSut.Shared; 4 | using Serilog; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Host.UseSerilog((_, lcfg) => 9 | lcfg 10 | .ReadFrom.Configuration(builder.Configuration) 11 | .WriteTo.Console() 12 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 13 | .Enrich.WithMachineName() 14 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 15 | 16 | builder.AddAkkaClusterSharding(); 17 | builder.Services.AddHostedService(); 18 | 19 | var app = builder.Build(); 20 | app.Run(); -------------------------------------------------------------------------------- /AkkaSut.Shared/AkkaSut.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Library 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /azure/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "namespaces_aborleans_name": { 6 | "value": null 7 | }, 8 | "storageAccounts_aborleans_name": { 9 | "value": null 10 | }, 11 | "managedClusters_ab_k8s_name": { 12 | "value": null 13 | }, 14 | "registries_abimgregistry_name": { 15 | "value": null 16 | }, 17 | "publicIPAddresses_fede5ea6_3328_4298_8b2c_69a6a7848948_externalid": { 18 | "value": null 19 | }, 20 | "userAssignedIdentities_ab_k8s_agentpool_externalid": { 21 | "value": null 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /TestRunner/Akka/AkkaExtensions.cs: -------------------------------------------------------------------------------- 1 | using TestRunner.Tests; 2 | 3 | namespace TestRunner.Akka; 4 | 5 | public static class AkkaExtensions 6 | { 7 | public static WebApplicationBuilder AddAkkaClusterProxyHostedService(this WebApplicationBuilder builder) 8 | { 9 | builder.Services.AddHostedService(); 10 | return builder; 11 | } 12 | 13 | public static WebApplicationBuilder AddAkkaTestServices(this WebApplicationBuilder builder) 14 | { 15 | builder.Services.AddSingleton(); 16 | builder.Services.AddSingleton(ctx => ctx.GetRequiredService().Ping); 17 | builder.Services.AddSingleton(ctx => ctx.GetRequiredService().Activate); 18 | 19 | return builder; 20 | } 21 | } -------------------------------------------------------------------------------- /OrleansSut.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using OrleansSut.Server; 3 | using OrleansSut.Shared; 4 | using Serilog; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | try 9 | { 10 | builder.Host.UseSerilog((_, lcfg) => 11 | lcfg 12 | .ReadFrom.Configuration(builder.Configuration) 13 | .WriteTo.Console() 14 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 15 | .Enrich.WithMachineName() 16 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 17 | 18 | builder.AddOrleans(typeof(PingPongGrain).Assembly); 19 | 20 | var app = builder.Build(); 21 | 22 | app.Run(); 23 | } 24 | catch (Exception e) 25 | { 26 | Log.Logger.Fatal(e, "Service crash"); 27 | throw; 28 | } 29 | finally 30 | { 31 | Log.CloseAndFlush(); 32 | } -------------------------------------------------------------------------------- /ProtoActorSut.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Proto; 3 | using ProtoActorSut.Contracts; 4 | using ProtoActorSut.Server; 5 | using ProtoActorSut.Shared; 6 | using Serilog; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | builder.Host.UseSerilog((_, lcfg) => 11 | lcfg 12 | .ReadFrom.Configuration(builder.Configuration) 13 | .WriteTo.Console() 14 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 15 | .Enrich.WithMachineName() 16 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 17 | 18 | 19 | builder.AddProtoActor( 20 | (PingPongActorActor.Kind, Props.FromProducer(() => new PingPongActorActor((c, _) => new PingPongActor(c)))), 21 | (Consts.PingPongRawKind, Props.FromProducer(() => new PingPongActorRaw()))); 22 | 23 | var app = builder.Build(); 24 | app.Run(); -------------------------------------------------------------------------------- /DaprSut.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using DaprSut.Server; 3 | using Serilog; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | try 8 | { 9 | builder.Host.UseSerilog((_, lcfg) => 10 | lcfg 11 | .ReadFrom.Configuration(builder.Configuration) 12 | .WriteTo.Console() 13 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 14 | .Enrich.WithMachineName() 15 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 16 | 17 | builder.Services.AddActors(opt => opt.Actors.RegisterActor()); 18 | 19 | var app = builder.Build(); 20 | 21 | app.MapActorsHandlers(); 22 | 23 | app.Run(); 24 | } 25 | catch (Exception e) 26 | { 27 | Log.Logger.Fatal(e, "Service crash"); 28 | throw; 29 | } 30 | finally 31 | { 32 | Log.CloseAndFlush(); 33 | } -------------------------------------------------------------------------------- /TestRunner/Tests/TestManager.cs: -------------------------------------------------------------------------------- 1 | namespace TestRunner.Tests; 2 | 3 | public class TestManager 4 | { 5 | Task _currentTest = Task.CompletedTask; 6 | CancellationTokenSource _cts = new(); 7 | readonly object _syncRoot = new(); 8 | 9 | public void TrackTest(Func runTest) 10 | { 11 | lock (_syncRoot) 12 | { 13 | if (TestIsCurrentlyRunning()) 14 | throw new Exception("Test is currently running, cannot run another one"); 15 | 16 | _cts = new(); 17 | _currentTest = runTest(_cts.Token); 18 | } 19 | } 20 | 21 | public Task CancelTest() 22 | { 23 | lock (_syncRoot) 24 | { 25 | if (!TestIsCurrentlyRunning()) return Task.CompletedTask; 26 | _cts.Cancel(); 27 | return _currentTest; 28 | } 29 | } 30 | 31 | private bool TestIsCurrentlyRunning() => !_currentTest.IsCompleted; 32 | } -------------------------------------------------------------------------------- /docker/Dockerfile-TestRunner: -------------------------------------------------------------------------------- 1 | # Stage 1 - Prepare base image 2 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base 3 | WORKDIR /app 4 | 5 | RUN addgroup --system --gid 101 app \ 6 | && adduser --system --ingroup app --uid 101 app 7 | 8 | ENV ASPNETCORE_URLS=http://*:5000 9 | ENV DOTNET_ReadyToRun=0 10 | ENV DOTNET_TC_QuickJitForLoops=1 11 | ENV DOTNET_TieredPGO=1 12 | 13 | EXPOSE 5000 14 | 15 | # Stage 2 - Build 16 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS builder 17 | 18 | WORKDIR /app/src 19 | 20 | COPY *.sln ./ 21 | COPY */*.csproj ./ 22 | RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done 23 | 24 | RUN dotnet restore -r linux-x64 25 | 26 | # Build 27 | COPY . . 28 | RUN dotnet publish -c Release -o /app/publish -r linux-x64 --no-self-contained --no-restore TestRunner 29 | 30 | # Stage 3 - Publish 31 | FROM base as final 32 | 33 | COPY --from=builder --chown=app:app /app/publish . 34 | 35 | USER app 36 | ENTRYPOINT ["./TestRunner"] -------------------------------------------------------------------------------- /docker/Dockerfile-AkkaSut: -------------------------------------------------------------------------------- 1 | # Stage 1 - Prepare base image 2 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base 3 | WORKDIR /app 4 | 5 | RUN addgroup --system --gid 101 app \ 6 | && adduser --system --ingroup app --uid 101 app 7 | 8 | ENV ASPNETCORE_URLS=http://*:5000 9 | ENV DOTNET_ReadyToRun=0 10 | ENV DOTNET_TC_QuickJitForLoops=1 11 | ENV DOTNET_TieredPGO=1 12 | 13 | EXPOSE 5000 14 | 15 | # Stage 2 - Build 16 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS builder 17 | 18 | WORKDIR /app/src 19 | 20 | COPY *.sln ./ 21 | COPY */*.csproj ./ 22 | RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done 23 | 24 | RUN dotnet restore -r linux-x64 25 | 26 | # Build 27 | COPY . . 28 | RUN dotnet publish -c Release -o /app/publish -r linux-x64 --no-self-contained --no-restore AkkaSut.Server 29 | 30 | # Stage 3 - Publish 31 | FROM base as final 32 | 33 | COPY --from=builder --chown=app:app /app/publish . 34 | 35 | USER app 36 | ENTRYPOINT ["./AkkaSut.Server"] -------------------------------------------------------------------------------- /docker/Dockerfile-DaprSut: -------------------------------------------------------------------------------- 1 | # Stage 1 - Prepare base image 2 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base 3 | WORKDIR /app 4 | 5 | RUN addgroup --system --gid 101 app \ 6 | && adduser --system --ingroup app --uid 101 app 7 | 8 | ENV ASPNETCORE_URLS=http://*:5000 9 | ENV DOTNET_ReadyToRun=0 10 | ENV DOTNET_TC_QuickJitForLoops=1 11 | ENV DOTNET_TieredPGO=1 12 | 13 | EXPOSE 5000 14 | 15 | # Stage 2 - Build 16 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS builder 17 | 18 | WORKDIR /app/src 19 | 20 | COPY *.sln ./ 21 | COPY */*.csproj ./ 22 | RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done 23 | 24 | RUN dotnet restore -r linux-x64 25 | 26 | # Build 27 | COPY . . 28 | RUN dotnet publish -c Release -o /app/publish -r linux-x64 --no-self-contained --no-restore DaprSut.Server 29 | 30 | # Stage 3 - Publish 31 | FROM base as final 32 | 33 | COPY --from=builder --chown=app:app /app/publish . 34 | 35 | USER app 36 | ENTRYPOINT ["./DaprSut.Server"] -------------------------------------------------------------------------------- /TestRunner/Dapr/DaprTestServices.cs: -------------------------------------------------------------------------------- 1 | using Dapr.Actors; 2 | using Dapr.Actors.Client; 3 | using DaprSut.Shared; 4 | 5 | namespace TestRunner.Dapr; 6 | 7 | public class DaprTestServices 8 | { 9 | public static async Task Ping(object handle, string name) 10 | { 11 | var proxy = handle as IPingPongActor ?? 12 | throw new ArgumentException($"Handle needs to be of type {nameof(IPingPongActor)}", nameof(handle)); 13 | 14 | var pong = await proxy.Ping(new PingMessage {Name = name}); 15 | 16 | var expectedResponse = "Hello " + name; 17 | 18 | if (pong.Response != expectedResponse) 19 | throw new Exception($"Received response '{pong.Response}' but expected '{expectedResponse}'"); 20 | } 21 | 22 | public static async Task Activate(string id) 23 | { 24 | var proxy = ActorProxy.Create(new ActorId(id), "PingPongActor"); 25 | await proxy.Ping(new PingMessage()); 26 | return proxy; 27 | } 28 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | seq: 6 | image: datalust/seq:2022.1 7 | environment: 8 | - ACCEPT_EULA=Y 9 | ports: 10 | - "5341:80" 11 | deploy: 12 | resources: 13 | limits: 14 | memory: 512m 15 | 16 | rabbitmq: 17 | image: rabbitmq:3-management 18 | ports: 19 | - "5672:5672" 20 | - "15672:15672" 21 | environment: 22 | - RABBITMQ_DEFAULT_USER=guest 23 | - RABBITMQ_DEFAULT_PASS=guest 24 | 25 | consul: 26 | image: bitnami/consul:1 27 | ports: 28 | - "8300:8300" 29 | - "8301:8301" 30 | - "8301:8301/udp" 31 | - "8500:8500" 32 | - "8600:8600" 33 | - "8600:8600/udp" 34 | 35 | lighthouse: 36 | image: petabridge/lighthouse:linux-latest 37 | environment: 38 | ACTORSYSTEM: actor-benchmark 39 | CLUSTER_PORT: 5800 40 | CLUSTER_IP: localhost 41 | ports: 42 | - "5800:5800" 43 | - "9110:9110" 44 | -------------------------------------------------------------------------------- /docker/Dockerfile-OrleansSut: -------------------------------------------------------------------------------- 1 | # Stage 1 - Prepare base image 2 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base 3 | WORKDIR /app 4 | 5 | RUN addgroup --system --gid 101 app \ 6 | && adduser --system --ingroup app --uid 101 app 7 | 8 | ENV ASPNETCORE_URLS=http://*:5000 9 | ENV DOTNET_ReadyToRun=0 10 | ENV DOTNET_TC_QuickJitForLoops=1 11 | ENV DOTNET_TieredPGO=1 12 | 13 | EXPOSE 5000 14 | EXPOSE 11111 15 | EXPOSE 30000 16 | 17 | # Stage 2 - Build 18 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS builder 19 | 20 | WORKDIR /app/src 21 | 22 | COPY *.sln ./ 23 | COPY */*.csproj ./ 24 | RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done 25 | 26 | RUN dotnet restore -r linux-x64 27 | 28 | # Build 29 | COPY . . 30 | RUN dotnet publish -c Release -o /app/publish -r linux-x64 --no-self-contained --no-restore OrleansSut.Server 31 | 32 | # Stage 3 - Publish 33 | FROM base as final 34 | 35 | COPY --from=builder --chown=app:app /app/publish . 36 | 37 | USER app 38 | ENTRYPOINT ["./OrleansSut.Server"] -------------------------------------------------------------------------------- /OrleansSut.Shared/OrleansSut.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Library 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docker/Dockerfile-ProtoActorSut: -------------------------------------------------------------------------------- 1 | # Stage 1 - Prepare base image 2 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base 3 | WORKDIR /app 4 | 5 | RUN addgroup --system --gid 101 app \ 6 | && adduser --system --ingroup app --uid 101 app 7 | 8 | ENV ASPNETCORE_URLS=http://*:5000 9 | ENV ProtoActor__AdvertisedPort=12121 10 | ENV DOTNET_ReadyToRun=0 11 | ENV DOTNET_TC_QuickJitForLoops=1 12 | ENV DOTNET_TieredPGO=1 13 | 14 | EXPOSE 5000 15 | EXPOSE 12121 16 | 17 | # Stage 2 - Build 18 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS builder 19 | 20 | WORKDIR /app/src 21 | 22 | COPY *.sln ./ 23 | COPY */*.csproj ./ 24 | RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done 25 | 26 | RUN dotnet restore -r linux-x64 27 | 28 | # Build 29 | COPY . . 30 | RUN dotnet publish -c Release -o /app/publish -r linux-x64 --no-self-contained --no-restore ProtoActorSut.Server 31 | 32 | # Stage 3 - Publish 33 | FROM base as final 34 | 35 | COPY --from=builder --chown=app:app /app/publish . 36 | 37 | USER app 38 | ENTRYPOINT ["./ProtoActorSut.Server"] -------------------------------------------------------------------------------- /ProtoActorSut.Shared/ActorSystemHostedService.cs: -------------------------------------------------------------------------------- 1 | using Proto; 2 | using Proto.Cluster; 3 | 4 | namespace ProtoActorSut.Shared; 5 | 6 | public class ActorSystemHostedService : IHostedService 7 | { 8 | private readonly ActorSystem _actorSystem; 9 | private readonly ILogger _logger; 10 | 11 | public ActorSystemHostedService(ActorSystem actorSystem, ILogger logger) 12 | { 13 | _actorSystem = actorSystem; 14 | _logger = logger; 15 | } 16 | 17 | public async Task StartAsync(CancellationToken cancellationToken) 18 | { 19 | _logger.LogInformation("Starting Proto actor system"); 20 | 21 | await _actorSystem 22 | .Cluster() 23 | .StartMemberAsync(); 24 | } 25 | 26 | public async Task StopAsync(CancellationToken cancellationToken) 27 | { 28 | _logger.LogInformation("Stopping Proto actor system"); 29 | 30 | await _actorSystem 31 | .Cluster() 32 | .ShutdownAsync(); 33 | } 34 | } -------------------------------------------------------------------------------- /TestRunner/Orleans/OrleansTestServices.cs: -------------------------------------------------------------------------------- 1 | using Orleans; 2 | using OrleansSut.Shared; 3 | 4 | namespace TestRunner.Orleans; 5 | 6 | public class OrleansTestServices 7 | { 8 | private readonly IClusterClient _client; 9 | 10 | public OrleansTestServices(IClusterClient client) => _client = client; 11 | 12 | public async Task Ping(object handle, string name) 13 | { 14 | var grain = handle as IPingPongGrain ?? 15 | throw new ArgumentException($"Handle needs to be of type {nameof(IPingPongGrain)}", nameof(handle)); 16 | 17 | var pong = await grain.Ping(new PingMessage(name)); 18 | 19 | var expectedResponse = "Hello " + name; 20 | 21 | if (pong.Response != expectedResponse) 22 | throw new Exception($"Received response '{pong.Response}' but expected '{expectedResponse}'"); 23 | } 24 | 25 | public async Task Activate(string id) 26 | { 27 | var grain = _client.GetGrain(id); 28 | await grain.Ping(new PingMessage(string.Empty)); 29 | return grain; 30 | } 31 | } -------------------------------------------------------------------------------- /TestRunner/Akka/ClusterProxyHostedService.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Sharding; 3 | using AkkaSut.Shared; 4 | 5 | namespace TestRunner.Akka; 6 | 7 | public class ClusterProxyHostedService : IHostedService 8 | { 9 | private readonly ClusterSharding _sharding; 10 | private readonly ActorSystem _actorSystem; 11 | private readonly IConfiguration _config; 12 | 13 | public ClusterProxyHostedService(ClusterSharding sharding, ActorSystem actorSystem, IConfiguration config) 14 | { 15 | _sharding = sharding; 16 | _actorSystem = actorSystem; 17 | _config = config; 18 | } 19 | 20 | public async Task StartAsync(CancellationToken cancellationToken) 21 | { 22 | await _sharding.StartProxyAsync( 23 | typeName: Consts.PingPongShardTypeName, 24 | _config["Akka:SutRole"], 25 | new MessageExtractor(maxNumberOfShards: 30)); 26 | } 27 | 28 | public Task StopAsync(CancellationToken cancellationToken) => 29 | CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance); 30 | } -------------------------------------------------------------------------------- /TestRunner/Monitoring/TestMetrics.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.Metrics; 2 | using OpenTelemetry.Metrics; 3 | 4 | namespace TestRunner.Monitoring; 5 | 6 | public static class TestMetrics 7 | { 8 | public const string MeterName = "TestRunner"; 9 | 10 | public static Meter TestMeter = new(MeterName); 11 | 12 | public static Counter MessageCount = TestMeter.CreateCounter("app_message_total"); 13 | 14 | public static Counter ErrorCount = TestMeter.CreateCounter("app_errors_total"); 15 | 16 | public static Histogram 17 | MessageLatency = TestMeter.CreateHistogram("app_message_latency", "seconds"); 18 | 19 | public static MeterProviderBuilder AddTestMetrics(this MeterProviderBuilder builder) 20 | { 21 | builder 22 | .AddMeter(MeterName) 23 | .AddView("app_message_latency", new ExplicitBucketHistogramConfiguration 24 | { 25 | Boundaries = new[] {0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10} 26 | }); 27 | 28 | return builder; 29 | } 30 | } -------------------------------------------------------------------------------- /AkkaSut.Server/AkkaSut.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TestRunner/ProtoActor/ProtoActorExtensions.cs: -------------------------------------------------------------------------------- 1 | using TestRunner.Tests; 2 | 3 | namespace TestRunner.ProtoActor; 4 | 5 | public static class ProtoActorExtensions 6 | { 7 | public static WebApplicationBuilder AddProtoActorTestServices(this WebApplicationBuilder builder) 8 | { 9 | builder.Services.AddSingleton(); 10 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Ping); 11 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Activate); 12 | 13 | return builder; 14 | } 15 | 16 | public static WebApplicationBuilder AddProtoActorTestServicesRaw(this WebApplicationBuilder builder) 17 | { 18 | builder.Services.AddSingleton(); 19 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Ping); 20 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Activate); 21 | 22 | return builder; 23 | } 24 | } -------------------------------------------------------------------------------- /DaprSut.Server/DaprSut.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | true 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ProtoActorSut.Server/ProtoActorSut.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TestRunner/Akka/AkkaTestServices.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Sharding; 3 | using AkkaSut.Shared; 4 | 5 | namespace TestRunner.Akka; 6 | 7 | public class AkkaTestServices 8 | { 9 | private readonly IActorRef _shard; 10 | 11 | public AkkaTestServices(ClusterSharding clusterSharding) 12 | { 13 | _shard = clusterSharding.ShardRegion(Consts.PingPongShardTypeName); 14 | } 15 | 16 | public async Task Ping(object handle, string name) 17 | { 18 | var id = handle as string ?? 19 | throw new ArgumentException("Handle needs to be of type string", nameof(handle)); 20 | 21 | var pong = await _shard.Ask(new PingMessage(id, name)); 22 | 23 | var expectedResponse = "Hello " + name; 24 | 25 | if (pong.Response != expectedResponse) 26 | throw new Exception($"Received response '{pong.Response}' but expected '{expectedResponse}'"); 27 | } 28 | 29 | public async Task Activate(string id) 30 | { 31 | await _shard.Ask(new PingMessage(id, "")); 32 | return id; 33 | } 34 | } -------------------------------------------------------------------------------- /TestRunner/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ActorFramework": "Orleans", 3 | 4 | "Orleans": { 5 | "SiloPort": 11112, 6 | "gatewayPort": 30001, 7 | "ClusterId": "dev", 8 | "ServiceId": "ActorBenchmark", 9 | "ClusteringStorage": "_storage_connection_string_" 10 | }, 11 | 12 | "ProtoActor": { 13 | "ClusterName": "dev" 14 | }, 15 | 16 | "Akka": { 17 | "ActorSystemName": "actor-benchmark", 18 | "Role": "runner", 19 | "SutRole": "sut", 20 | "PublicHostname": "localhost", 21 | "Port": 5056, 22 | "SeedNodes": ["localhost:5800"] 23 | }, 24 | 25 | "Bus": { 26 | "RabbitMq": { 27 | "Host": "localhost", 28 | "User": "guest", 29 | "Password": "guest" 30 | }, 31 | 32 | "ServiceBusConnectionString": "_service_bus_connection_string_" 33 | }, 34 | 35 | "SeqUrl": "http://localhost:5341", 36 | 37 | "Serilog": { 38 | "MinimumLevel": { 39 | "Default": "Information", 40 | "Override": { 41 | "Microsoft.AspNetCore": "Warning", 42 | "Microsoft.Hosting.Lifetime": "Information" 43 | } 44 | } 45 | }, 46 | 47 | "AllowedHosts": "*" 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Etteplan Tech Poland S.A. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /TestRunner.Client/TestRunner.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TestRunner/ProtoActor/ProtoActorTestServices.cs: -------------------------------------------------------------------------------- 1 | using Proto.Cluster; 2 | using ProtoActorSut.Contracts; 3 | 4 | namespace TestRunner.ProtoActor; 5 | 6 | public class ProtoActorTestServices 7 | { 8 | private readonly Cluster _cluster; 9 | 10 | public ProtoActorTestServices(Cluster cluster) => _cluster = cluster; 11 | 12 | public async Task Ping(object handle, string name) 13 | { 14 | var actor = handle as PingPongActorClient ?? 15 | throw new ArgumentException($"Handle needs to be of type {nameof(PingPongActorClient)}", nameof(handle)); 16 | 17 | var pong = await actor.Ping(new PingMessage { Name = name}, CancellationToken.None); 18 | 19 | var expectedResponse = "Hello " + name; 20 | 21 | if (pong?.Response != expectedResponse) 22 | throw new Exception($"Received response '{pong?.Response}' but expected '{expectedResponse}'"); 23 | } 24 | 25 | public async Task Activate(string id) 26 | { 27 | var actor = _cluster.GetPingPongActor(id); 28 | await actor.Ping(new PingMessage { Name = string.Empty}, CancellationToken.None); 29 | return actor; 30 | } 31 | } -------------------------------------------------------------------------------- /kubernetes/values-prometheus.yaml: -------------------------------------------------------------------------------- 1 | rbac: 2 | create: true 3 | 4 | alertmanager: 5 | enabled: false 6 | 7 | configmapReload: 8 | enabled: true 9 | 10 | kubeStateMetrics: 11 | enabled: true 12 | 13 | kube-state-metrics: 14 | affinity: 15 | nodeAffinity: 16 | requiredDuringSchedulingIgnoredDuringExecution: 17 | nodeSelectorTerms: 18 | - matchExpressions: 19 | - key: test-role 20 | operator: In 21 | values: 22 | - management 23 | 24 | nodeExporter: 25 | enabled: true 26 | 27 | server: 28 | enabled: true 29 | global: 30 | scrape_interval: 15s 31 | 32 | persistentVolume: 33 | enabled: true 34 | storageClass: managed 35 | size: 8Gi 36 | 37 | strategy: 38 | type: Recreate 39 | 40 | resources: 41 | limits: 42 | memory: 2Gi 43 | 44 | affinity: 45 | nodeAffinity: 46 | requiredDuringSchedulingIgnoredDuringExecution: 47 | nodeSelectorTerms: 48 | - matchExpressions: 49 | - key: test-role 50 | operator: In 51 | values: 52 | - management 53 | 54 | pushgateway: 55 | enabled: false -------------------------------------------------------------------------------- /ProtoActorSut.Shared/ProtoActorSut.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Library 8 | false 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /AkkaSut.Server/AkkaClusterService.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Sharding; 3 | using AkkaSut.Shared; 4 | 5 | namespace AkkaSut.Server; 6 | 7 | public class AkkaClusterService : IHostedService 8 | { 9 | private readonly ActorSystem _actorSystem; 10 | private readonly ClusterSharding _sharding; 11 | private readonly IConfiguration _config; 12 | 13 | public AkkaClusterService(ActorSystem actorSystem, ClusterSharding sharding, IConfiguration config) 14 | { 15 | _actorSystem = actorSystem; 16 | _sharding = sharding; 17 | _config = config; 18 | } 19 | 20 | public async Task StartAsync(CancellationToken cancellationToken) 21 | { 22 | var pingPongProps = Props.Create(() => new PingPongActor()); 23 | 24 | 25 | await _sharding.StartAsync( 26 | typeName: Consts.PingPongShardTypeName, 27 | entityProps: pingPongProps, 28 | settings: ClusterShardingSettings.Create(_actorSystem).WithRole(_config["Akka:Role"]), 29 | messageExtractor: new MessageExtractor(30) 30 | ); 31 | 32 | } 33 | 34 | public Task StopAsync(CancellationToken cancellationToken) => 35 | CoordinatedShutdown.Get(_actorSystem).Run(CoordinatedShutdown.ClrExitReason.Instance); 36 | } -------------------------------------------------------------------------------- /TestRunner/Bus/RunTestConsumer.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | using TestRunner.Contract; 3 | using TestRunner.Tests; 4 | 5 | namespace TestRunner.Bus; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | public class RunTestConsumer : IConsumer, IConsumer 9 | { 10 | private readonly TestManager _manager; 11 | private readonly IServiceProvider _provider; 12 | 13 | public RunTestConsumer(TestManager manager, IServiceProvider provider) 14 | { 15 | _manager = manager; 16 | _provider = provider; 17 | } 18 | 19 | public Task Consume(ConsumeContext context) 20 | { 21 | var test = _provider.GetRequiredService(); 22 | _manager.TrackTest(cancel => 23 | test.RunTest(context.Message.Parallelism, context.Message.DurationInSeconds, cancel)); 24 | 25 | return Task.CompletedTask; 26 | } 27 | 28 | public Task Consume(ConsumeContext context) 29 | { 30 | var test = _provider.GetRequiredService(); 31 | _manager.TrackTest(cancel => 32 | test.RunTest(context.Message.ActivationCount, context.Message.Parallelism, cancel)); 33 | 34 | return Task.CompletedTask; 35 | } 36 | } -------------------------------------------------------------------------------- /TestRunner/ProtoActor/ProtoActorTestServicesRaw.cs: -------------------------------------------------------------------------------- 1 | using Proto; 2 | using Proto.Cluster; 3 | using ProtoActorSut.Contracts; 4 | using ProtoActorSut.Shared; 5 | 6 | namespace TestRunner.ProtoActor; 7 | 8 | public class ProtoActorTestServicesRaw 9 | { 10 | private readonly Cluster _cluster; 11 | 12 | public ProtoActorTestServicesRaw(Cluster cluster) => _cluster = cluster; 13 | 14 | public async Task Ping(object handle, string name) 15 | { 16 | var ci = handle as ClusterIdentity ?? 17 | throw new ArgumentException($"Handle needs to be of type {nameof(ClusterIdentity)}", nameof(handle)); 18 | 19 | var pong = await _cluster.RequestAsync(ci, new PingMessage { Name = name}, CancellationTokens.FromSeconds(5)); 20 | 21 | var expectedResponse = "Hello " + name; 22 | 23 | if (pong == null) 24 | { 25 | throw new Exception("Request timed out"); 26 | } 27 | if (pong?.Response != expectedResponse) 28 | throw new Exception($"Received response '{pong?.Response}' but expected '{expectedResponse}'"); 29 | } 30 | 31 | public async Task Activate(string id) 32 | { 33 | var ci = ClusterIdentity.Create(id, Consts.PingPongRawKind); 34 | var res = await _cluster.RequestAsync(ci, new PingMessage(), CancellationToken.None); 35 | return ci; 36 | } 37 | } -------------------------------------------------------------------------------- /OrleansSut.Server/OrleansSut.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /kubernetes/deployment-dapr-sut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dapr-sut 5 | labels: 6 | app: dapr-sut 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: dapr-sut 12 | template: 13 | metadata: 14 | annotations: 15 | prometheus.io/scrape: "false" 16 | dapr.io/enabled: "true" 17 | dapr.io/app-id: actor-benchmark 18 | dapr.io/app-port: "5000" 19 | dapr.io/config: actor-benchmark-config 20 | labels: 21 | app: dapr-sut 22 | orleans/serviceId: actor-benchmark 23 | orleans/clusterId: actor-benchmark 24 | spec: 25 | containers: 26 | - name: main 27 | image: abimgregistry.azurecr.io/dapr-sut:latest 28 | imagePullPolicy: Always 29 | env: 30 | - name: SeqUrl 31 | value: http://my-seq.seq:5341 32 | terminationGracePeriodSeconds: 180 33 | affinity: 34 | nodeAffinity: 35 | requiredDuringSchedulingIgnoredDuringExecution: 36 | nodeSelectorTerms: 37 | - matchExpressions: 38 | - key: test-role 39 | operator: In 40 | values: 41 | - sut 42 | podAntiAffinity: 43 | requiredDuringSchedulingIgnoredDuringExecution: 44 | - topologyKey: "kubernetes.io/hostname" 45 | labelSelector: 46 | matchExpressions: 47 | - key: app 48 | operator: In 49 | values: 50 | - dapr-sut 51 | -------------------------------------------------------------------------------- /OrleansSut.Shared/OrleansExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Orleans; 3 | using Orleans.Configuration; 4 | using Orleans.Hosting; 5 | 6 | namespace OrleansSut.Shared; 7 | 8 | public static class OrleansExtensions 9 | { 10 | public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builder, params Assembly[] applicationParts) 11 | { 12 | builder.Host.UseOrleans(siloBuilder => 13 | { 14 | var config = builder.Configuration.GetSection("Orleans"); 15 | 16 | if (builder.Environment.IsDevelopment()) 17 | { 18 | siloBuilder 19 | .Configure(opt => 20 | { 21 | opt.ClusterId = config["ClusterId"]; 22 | opt.ServiceId = config["ServiceId"]; 23 | }) 24 | .ConfigureEndpoints( 25 | config.GetValue("SiloPort", 11111), 26 | config.GetValue("GatewayPort", 30000) 27 | ); 28 | } 29 | else 30 | { 31 | siloBuilder.UseKubernetesHosting(); 32 | } 33 | 34 | siloBuilder 35 | .UseAzureStorageClustering(options => options.ConfigureTableServiceClient(config["ClusteringStorage"])); 36 | 37 | if (applicationParts.Any()) 38 | { 39 | siloBuilder.ConfigureApplicationParts(parts => 40 | { 41 | foreach (var applicationPart in applicationParts) 42 | parts.AddApplicationPart(applicationPart).WithReferences(); 43 | }); 44 | } 45 | }); 46 | 47 | return builder; 48 | } 49 | } -------------------------------------------------------------------------------- /kubernetes/deployment-test-runner-dapr.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-runner 5 | labels: 6 | app: test-runner 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: test-runner 12 | template: 13 | metadata: 14 | annotations: 15 | prometheus.io/scrape: "true" 16 | prometheus.io/port: "5000" 17 | dapr.io/enabled: "true" 18 | dapr.io/app-id: actor-benchmark 19 | dapr.io/app-port: "5000" 20 | dapr.io/config: actor-benchmark-config 21 | labels: 22 | app: test-runner 23 | spec: 24 | containers: 25 | - name: main 26 | image: abimgregistry.azurecr.io/test-runner:latest 27 | imagePullPolicy: Always 28 | env: 29 | - name: ActorFramework 30 | value: Dapr 31 | - name: Bus__ServiceBusConnectionString 32 | value: _service_bus_connection_string_ 33 | - name: SeqUrl 34 | value: http://my-seq.seq:5341 35 | terminationGracePeriodSeconds: 180 36 | affinity: 37 | nodeAffinity: 38 | requiredDuringSchedulingIgnoredDuringExecution: 39 | nodeSelectorTerms: 40 | - matchExpressions: 41 | - key: test-role 42 | operator: In 43 | values: 44 | - runner 45 | podAntiAffinity: 46 | requiredDuringSchedulingIgnoredDuringExecution: 47 | - topologyKey: "kubernetes.io/hostname" 48 | labelSelector: 49 | matchExpressions: 50 | - key: app 51 | operator: In 52 | values: 53 | - test-runner 54 | -------------------------------------------------------------------------------- /kubernetes/deployment-akka-sut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: akka-sut 5 | labels: 6 | app: akka-sut 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: akka-sut 12 | template: 13 | metadata: 14 | annotations: 15 | prometheus.io/scrape: "false" 16 | labels: 17 | app: akka-sut 18 | spec: 19 | containers: 20 | - name: main 21 | image: abimgregistry.azurecr.io/akka-sut:latest 22 | imagePullPolicy: Always 23 | ports: 24 | - containerPort: 4053 25 | env: 26 | - name: SeqUrl 27 | value: http://my-seq.seq:5341 28 | - name: Akka__ActorSystemName 29 | value: actor-benchmark 30 | - name: Akka__PublicHostname 31 | valueFrom: 32 | fieldRef: 33 | fieldPath: status.podIP 34 | - name: Akka__Port 35 | value: "4053" 36 | - name: Akka__SeedNodes__0 37 | value: lighthouse-0.lighthouse:4053 38 | - name: Akka__SeedNodes__1 39 | value: lighthouse-1.lighthouse:4053 40 | affinity: 41 | nodeAffinity: 42 | requiredDuringSchedulingIgnoredDuringExecution: 43 | nodeSelectorTerms: 44 | - matchExpressions: 45 | - key: test-role 46 | operator: In 47 | values: 48 | - sut 49 | podAntiAffinity: 50 | requiredDuringSchedulingIgnoredDuringExecution: 51 | - topologyKey: "kubernetes.io/hostname" 52 | labelSelector: 53 | matchExpressions: 54 | - key: app 55 | operator: In 56 | values: 57 | - akka-sut 58 | -------------------------------------------------------------------------------- /TestRunner/TestRunner.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /kubernetes/statefulset-lighthouse.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: lighthouse 5 | labels: 6 | app: lighthouse 7 | spec: 8 | clusterIP: None 9 | ports: 10 | - port: 4053 11 | selector: 12 | app: lighthouse 13 | --- 14 | apiVersion: apps/v1 15 | kind: StatefulSet 16 | metadata: 17 | name: lighthouse 18 | labels: 19 | app: lighthouse 20 | spec: 21 | serviceName: lighthouse 22 | replicas: 2 23 | selector: 24 | matchLabels: 25 | app: lighthouse 26 | template: 27 | metadata: 28 | labels: 29 | app: lighthouse 30 | spec: 31 | terminationGracePeriodSeconds: 35 32 | containers: 33 | - name: lighthouse 34 | image: petabridge/lighthouse:latest 35 | lifecycle: 36 | preStop: 37 | exec: 38 | command: ["/bin/sh", "-c", "pbm 127.0.0.1:9110 cluster leave"] 39 | env: 40 | - name: ACTORSYSTEM 41 | value: actor-benchmark 42 | - name: POD_NAME 43 | valueFrom: 44 | fieldRef: 45 | fieldPath: metadata.name 46 | - name: CLUSTER_IP 47 | value: "$(POD_NAME).lighthouse" 48 | - name: CLUSTER_SEEDS 49 | value: akka.tcp://$(ACTORSYSTEM)@lighthouse-0.lighthouse:4053,akka.tcp://$(ACTORSYSTEM)@lighthouse-1.lighthouse:4053 50 | livenessProbe: 51 | tcpSocket: 52 | port: 4053 53 | ports: 54 | - containerPort: 4053 55 | protocol: TCP 56 | affinity: 57 | nodeAffinity: 58 | requiredDuringSchedulingIgnoredDuringExecution: 59 | nodeSelectorTerms: 60 | - matchExpressions: 61 | - key: test-role 62 | operator: In 63 | values: 64 | - management -------------------------------------------------------------------------------- /TestRunner/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "TestRunner Orleans": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": false, 8 | "applicationUrl": "https://localhost:7032;http://localhost:5032", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "ActorFramework": "Orleans" 12 | } 13 | }, 14 | "TestRunner ProtoActor": { 15 | "commandName": "Project", 16 | "dotnetRunMessages": true, 17 | "launchBrowser": false, 18 | "applicationUrl": "https://localhost:7032;http://localhost:5032", 19 | "environmentVariables": { 20 | "ASPNETCORE_ENVIRONMENT": "Development", 21 | "ActorFramework": "ProtoActor" 22 | } 23 | }, 24 | "TestRunner ProtoActor Raw": { 25 | "commandName": "Project", 26 | "dotnetRunMessages": true, 27 | "launchBrowser": false, 28 | "applicationUrl": "https://localhost:7032;http://localhost:5032", 29 | "environmentVariables": { 30 | "ASPNETCORE_ENVIRONMENT": "Development", 31 | "ActorFramework": "ProtoActorRaw" 32 | } 33 | }, 34 | "TestRunner Akka": { 35 | "commandName": "Project", 36 | "dotnetRunMessages": true, 37 | "launchBrowser": false, 38 | "applicationUrl": "https://localhost:7032;http://localhost:5032", 39 | "environmentVariables": { 40 | "ASPNETCORE_ENVIRONMENT": "Development", 41 | "ActorFramework": "Akka" 42 | } 43 | }, 44 | "TestRunner Dapr": { 45 | "commandName": "Project", 46 | "dotnetRunMessages": true, 47 | "launchBrowser": false, 48 | "applicationUrl": "https://localhost:7032;http://localhost:5032", 49 | "environmentVariables": { 50 | "ASPNETCORE_ENVIRONMENT": "Development", 51 | "ActorFramework": "Dapr" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TestRunner/Bus/MassTransitExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MassTransit; 3 | 4 | namespace TestRunner.Bus; 5 | 6 | public static class MassTransitExtensions 7 | { 8 | public static void AddMassTransit(this WebApplicationBuilder builder) 9 | { 10 | var config = builder.Configuration.GetSection("Bus"); 11 | var endpoint = Environment.MachineName; 12 | 13 | builder.Services.AddMassTransit(busCfg => 14 | { 15 | if (builder.Environment.IsDevelopment()) 16 | { 17 | busCfg.UsingRabbitMq((ctx, cfg) => 18 | { 19 | cfg.Host(config["RabbitMq:Host"], host => 20 | { 21 | host.Username(config["RabbitMq:User"]); 22 | host.Password(config["RabbitMq:Password"]); 23 | }); 24 | 25 | cfg.ReceiveEndpoint(endpoint, e => 26 | { 27 | e.AutoDelete = true; 28 | e.Durable = false; 29 | 30 | e.ConfigureConsumer(ctx); 31 | }); 32 | 33 | }); 34 | } 35 | else 36 | { 37 | busCfg.UsingAzureServiceBus((ctx, cfg) => 38 | { 39 | cfg.Host(config["ServiceBusConnectionString"]); 40 | 41 | cfg.ReceiveEndpoint(endpoint, e => 42 | { 43 | e.AutoDeleteOnIdle = TimeSpan.FromMinutes(10); 44 | e.RemoveSubscriptions = true; 45 | 46 | e.ConfigureConsumer(ctx); 47 | }); 48 | }); 49 | } 50 | 51 | busCfg.AddConsumers(Assembly.GetExecutingAssembly()); 52 | }); 53 | } 54 | } -------------------------------------------------------------------------------- /kubernetes/deployment-test-runner-akka.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-runner 5 | labels: 6 | app: test-runner 7 | spec: 8 | replicas: 3 9 | selector: 10 | matchLabels: 11 | app: test-runner 12 | template: 13 | metadata: 14 | annotations: 15 | prometheus.io/scrape: 'true' 16 | prometheus.io/port: '5000' 17 | labels: 18 | app: test-runner 19 | spec: 20 | containers: 21 | - name: main 22 | image: abimgregistry.azurecr.io/test-runner:latest 23 | imagePullPolicy: Always 24 | env: 25 | - name: ActorFramework 26 | value: Akka 27 | - name: Akka__ActorSystemName 28 | value: actor-benchmark 29 | - name: Akka__PublicHostname 30 | valueFrom: 31 | fieldRef: 32 | fieldPath: status.podIP 33 | - name: Akka__Port 34 | value: "4053" 35 | - name: Akka__SeedNodes__0 36 | value: lighthouse-0.lighthouse:4053 37 | - name: Akka__SeedNodes__1 38 | value: lighthouse-1.lighthouse:4053 39 | - name: Bus__ServiceBusConnectionString 40 | value: _servicebus_connection_string_ 41 | - name: SeqUrl 42 | value: http://my-seq.seq:5341 43 | terminationGracePeriodSeconds: 180 44 | affinity: 45 | nodeAffinity: 46 | requiredDuringSchedulingIgnoredDuringExecution: 47 | nodeSelectorTerms: 48 | - matchExpressions: 49 | - key: test-role 50 | operator: In 51 | values: 52 | - runner 53 | podAntiAffinity: 54 | requiredDuringSchedulingIgnoredDuringExecution: 55 | - topologyKey: "kubernetes.io/hostname" 56 | labelSelector: 57 | matchExpressions: 58 | - key: app 59 | operator: In 60 | values: 61 | - test-runner 62 | -------------------------------------------------------------------------------- /TestRunner.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MassTransit; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Serilog; 5 | using TestRunner.Contract; 6 | 7 | var builder = WebApplication.CreateBuilder(args); 8 | 9 | try 10 | { 11 | builder.Host.UseSerilog((_, lcfg) => 12 | lcfg 13 | .ReadFrom.Configuration(builder.Configuration) 14 | .WriteTo.Console() 15 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 16 | .Enrich.WithMachineName() 17 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 18 | 19 | builder.Services.AddMassTransit(busCfg => 20 | { 21 | if (builder.Environment.IsDevelopment()) 22 | { 23 | busCfg.UsingRabbitMq((ctx, cfg) => 24 | { 25 | cfg.Host(builder.Configuration["Bus:RabbitMq:Host"], host => 26 | { 27 | host.Username(builder.Configuration["Bus:RabbitMq:User"]); 28 | host.Password(builder.Configuration["Bus:RabbitMq:Password"]); 29 | }); 30 | }); 31 | } 32 | else 33 | { 34 | busCfg.UsingAzureServiceBus( 35 | (_, cfg) => 36 | cfg.Host(builder.Configuration["Bus:ServiceBusConnectionString"])); 37 | } 38 | }); 39 | 40 | builder.Services.AddEndpointsApiExplorer(); 41 | builder.Services.AddSwaggerGen(); 42 | 43 | var app = builder.Build(); 44 | 45 | app.UseSwagger(); 46 | app.UseSwaggerUI(); 47 | 48 | 49 | app.MapPost("/runMessagingTest", 50 | (HttpContext _, IPublishEndpoint publisher, [FromQuery] int parallelism, [FromQuery] int durationInSeconds) 51 | => publisher.Publish(new RunMessagingTest(parallelism, durationInSeconds))); 52 | 53 | app.MapPost("/runActivationTest", 54 | (HttpContext _, IPublishEndpoint publisher, [FromQuery] int activationCount, [FromQuery] int parallelism) 55 | => publisher.Publish(new RunActivationTest(activationCount, parallelism))); 56 | 57 | 58 | app.Run(); 59 | } 60 | catch (Exception e) 61 | { 62 | Log.Logger.Fatal(e, "Service crash"); 63 | throw; 64 | } 65 | finally 66 | { 67 | Log.CloseAndFlush(); 68 | } -------------------------------------------------------------------------------- /kubernetes/deployment-proto-actor-sut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: proto-actor-sut 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: proto-actor-sut-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - patch 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: proto-actor-sut-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: proto-actor-sut-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: proto-actor-sut 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: proto-actor-sut 37 | labels: 38 | app: proto-actor-sut 39 | spec: 40 | replicas: 3 41 | selector: 42 | matchLabels: 43 | app: proto-actor-sut 44 | template: 45 | metadata: 46 | annotations: 47 | prometheus.io/scrape: 'false' 48 | labels: 49 | app: proto-actor-sut 50 | spec: 51 | serviceAccountName: proto-actor-sut 52 | containers: 53 | - name: main 54 | image: abimgregistry.azurecr.io/proto-actor-sut:latest 55 | imagePullPolicy: Always 56 | ports: 57 | - containerPort: 12121 58 | env: 59 | - name: SeqUrl 60 | value: http://my-seq.seq:5341 61 | - name: ProtoActor__ClusterName 62 | value: actor-benchmark 63 | - name: ProtoActor__AdvertisedHost 64 | valueFrom: 65 | fieldRef: 66 | fieldPath: status.podIP 67 | affinity: 68 | nodeAffinity: 69 | requiredDuringSchedulingIgnoredDuringExecution: 70 | nodeSelectorTerms: 71 | - matchExpressions: 72 | - key: test-role 73 | operator: In 74 | values: 75 | - sut 76 | podAntiAffinity: 77 | requiredDuringSchedulingIgnoredDuringExecution: 78 | - topologyKey: "kubernetes.io/hostname" 79 | labelSelector: 80 | matchExpressions: 81 | - key: app 82 | operator: In 83 | values: 84 | - proto-actor-sut 85 | -------------------------------------------------------------------------------- /kubernetes/deployment-test-runner-proto-actor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: test-runner 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: test-runner-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - patch 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: test-runner-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: test-runner-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: test-runner 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: test-runner 37 | labels: 38 | app: test-runner 39 | spec: 40 | replicas: 3 41 | selector: 42 | matchLabels: 43 | app: test-runner 44 | template: 45 | metadata: 46 | annotations: 47 | prometheus.io/scrape: 'true' 48 | prometheus.io/port: '5000' 49 | labels: 50 | app: test-runner 51 | spec: 52 | serviceAccountName: test-runner 53 | containers: 54 | - name: main 55 | image: abimgregistry.azurecr.io/test-runner:latest 56 | imagePullPolicy: Always 57 | env: 58 | - name: ActorFramework 59 | value: ProtoActor 60 | - name: ProtoActor__ClusterName 61 | value: actor-benchmark 62 | - name: ProtoActor__AdvertisedHost 63 | valueFrom: 64 | fieldRef: 65 | fieldPath: status.podIP 66 | - name: Bus__ServiceBusConnectionString 67 | value: _service_bus_connection_string_ 68 | - name: SeqUrl 69 | value: http://my-seq.seq:5341 70 | terminationGracePeriodSeconds: 180 71 | affinity: 72 | nodeAffinity: 73 | requiredDuringSchedulingIgnoredDuringExecution: 74 | nodeSelectorTerms: 75 | - matchExpressions: 76 | - key: test-role 77 | operator: In 78 | values: 79 | - runner 80 | podAntiAffinity: 81 | requiredDuringSchedulingIgnoredDuringExecution: 82 | - topologyKey: "kubernetes.io/hostname" 83 | labelSelector: 84 | matchExpressions: 85 | - key: app 86 | operator: In 87 | values: 88 | - test-runner 89 | -------------------------------------------------------------------------------- /kubernetes/deployment-test-runner-proto-actor-raw.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: test-runner 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: test-runner-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - patch 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: test-runner-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: test-runner-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: test-runner 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: test-runner 37 | labels: 38 | app: test-runner 39 | spec: 40 | replicas: 3 41 | selector: 42 | matchLabels: 43 | app: test-runner 44 | template: 45 | metadata: 46 | annotations: 47 | prometheus.io/scrape: 'true' 48 | prometheus.io/port: '5000' 49 | labels: 50 | app: test-runner 51 | spec: 52 | serviceAccountName: test-runner 53 | containers: 54 | - name: main 55 | image: abimgregistry.azurecr.io/test-runner:latest 56 | imagePullPolicy: Always 57 | env: 58 | - name: ActorFramework 59 | value: ProtoActorRaw 60 | - name: ProtoActor__ClusterName 61 | value: actor-benchmark 62 | - name: ProtoActor__AdvertisedHost 63 | valueFrom: 64 | fieldRef: 65 | fieldPath: status.podIP 66 | - name: Bus__ServiceBusConnectionString 67 | value: _service_bus_connection_string_ 68 | - name: SeqUrl 69 | value: http://my-seq.seq:5341 70 | terminationGracePeriodSeconds: 180 71 | affinity: 72 | nodeAffinity: 73 | requiredDuringSchedulingIgnoredDuringExecution: 74 | nodeSelectorTerms: 75 | - matchExpressions: 76 | - key: test-role 77 | operator: In 78 | values: 79 | - runner 80 | podAntiAffinity: 81 | requiredDuringSchedulingIgnoredDuringExecution: 82 | - topologyKey: "kubernetes.io/hostname" 83 | labelSelector: 84 | matchExpressions: 85 | - key: app 86 | operator: In 87 | values: 88 | - test-runner 89 | -------------------------------------------------------------------------------- /TestRunner/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.Reflection; 3 | using AkkaSut.Shared; 4 | using OpenTelemetry.Metrics; 5 | using OpenTelemetry.Resources; 6 | using OrleansSut.Shared; 7 | using ProtoActorSut.Shared; 8 | using Serilog; 9 | using TestRunner.Akka; 10 | using TestRunner.Bus; 11 | using TestRunner.Dapr; 12 | using TestRunner.Monitoring; 13 | using TestRunner.Orleans; 14 | using TestRunner.ProtoActor; 15 | using TestRunner.Tests; 16 | 17 | var builder = WebApplication.CreateBuilder(args); 18 | 19 | try 20 | { 21 | builder.Host.UseSerilog((_, lcfg) => 22 | lcfg 23 | .ReadFrom.Configuration(builder.Configuration) 24 | .WriteTo.Console() 25 | .WriteTo.Seq(builder.Configuration["SeqUrl"]) 26 | .Enrich.WithMachineName() 27 | .Enrich.WithProperty("Service", Assembly.GetExecutingAssembly().GetName().Name)); 28 | 29 | var actorFramework = builder.Configuration["ActorFramework"]; 30 | 31 | switch (actorFramework) 32 | { 33 | case "Orleans": 34 | builder 35 | .AddOrleans() 36 | .AddOrleansTestServices(); 37 | break; 38 | case "ProtoActor": 39 | builder 40 | .AddProtoActor() 41 | .AddProtoActorTestServices(); 42 | break; 43 | case "ProtoActorRaw": 44 | builder 45 | .AddProtoActor() 46 | .AddProtoActorTestServicesRaw(); 47 | break; 48 | case "Akka": 49 | builder 50 | .AddAkkaClusterSharding() 51 | .AddAkkaClusterProxyHostedService() 52 | .AddAkkaTestServices(); 53 | break; 54 | case "Dapr": 55 | builder.AddDaprTestServices(); 56 | break; 57 | default: 58 | throw new ConfigurationErrorsException("Unknown framework " + actorFramework); 59 | } 60 | 61 | builder.AddMassTransit(); 62 | 63 | builder.Services.AddSingleton(); 64 | builder.Services.AddTransient(); 65 | builder.Services.AddTransient(); 66 | 67 | builder.Services.AddOpenTelemetryMetrics(b => b 68 | .SetResourceBuilder( 69 | ResourceBuilder.CreateDefault() 70 | .AddService(Assembly.GetExecutingAssembly().GetName().Name, serviceInstanceId: Environment.MachineName)) 71 | .AddTestMetrics() 72 | .AddPrometheusExporter(cfg => cfg.ScrapeResponseCacheDurationMilliseconds = 1000) 73 | ); 74 | 75 | var app = builder.Build(); 76 | 77 | app.UseRouting(); 78 | app.UseOpenTelemetryPrometheusScrapingEndpoint(); 79 | 80 | app.Run(); 81 | } 82 | catch (Exception e) 83 | { 84 | Log.Logger.Fatal(e, "Service crash"); 85 | throw; 86 | } 87 | finally 88 | { 89 | Log.CloseAndFlush(); 90 | } -------------------------------------------------------------------------------- /AkkaSut.Shared/ActorSystemExtensions.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | using Akka.Cluster.Sharding; 3 | using Akka.Configuration; 4 | using Akka.DependencyInjection; 5 | using Akka.Logger.Extensions.Logging; 6 | 7 | namespace AkkaSut.Shared; 8 | 9 | public static class ActorSystemExtensions 10 | { 11 | public static WebApplicationBuilder AddAkkaClusterSharding(this WebApplicationBuilder builder) 12 | { 13 | var config = builder.Configuration.GetSection("Akka"); 14 | 15 | var seedNodes = config.GetSection("SeedNodes").Get>(); 16 | var seedNodesAkkaConfig = seedNodes 17 | .Aggregate("", (acc, node) => $"{acc}\"akka.tcp://{config["ActorSystemName"]}@{node}\",") 18 | .TrimEnd(','); 19 | 20 | var hocon = @$" 21 | akka {{ 22 | actor.provider = cluster 23 | cluster.roles = [{config["Role"]}] 24 | 25 | remote.dot-netty.tcp {{ 26 | hostname = 0.0.0.0 27 | public-hostname = {config["PublicHostname"]} 28 | port = {config["Port"]} 29 | }} 30 | 31 | cluster.seed-nodes = [{seedNodesAkkaConfig}] 32 | 33 | loglevel = INFO 34 | loggers = [""Akka.Logger.Extensions.Logging.LoggingLogger, Akka.Logger.Extensions.Logging""] 35 | 36 | # shard coordinator uses snapshots, use in mem impl to avoid filesystem permission issues 37 | persistence.snapshot-store {{ 38 | plugin = ""akka.persistence.snapshot-store.inmem"" 39 | inmem {{ 40 | class = ""Akka.Persistence.Snapshot.MemorySnapshotStore, Akka.Persistence"" 41 | plugin-dispatcher = ""akka.actor.default-dispatcher"" 42 | }} 43 | }} 44 | 45 | actor {{ 46 | # use fast serialization instead of default newtonsoft json 47 | serializers {{ 48 | hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"" 49 | }} 50 | 51 | serialization-bindings {{ 52 | ""System.Object"" = hyperion 53 | }} 54 | }} 55 | }} 56 | "; 57 | 58 | builder.Services.AddSingleton(sp => 59 | { 60 | LoggingLogger.LoggerFactory = sp.GetRequiredService(); 61 | 62 | var bootstrap = BootstrapSetup.Create() 63 | .WithConfig(ConfigurationFactory.ParseString(hocon)); 64 | 65 | var serviceProviderSetup = DependencyResolverSetup.Create(sp); 66 | 67 | bootstrap.And(serviceProviderSetup); 68 | 69 | return ActorSystem.Create(config["ActorSystemName"], bootstrap); 70 | }); 71 | 72 | builder.Services.AddSingleton(sp => ClusterSharding.Get(sp.GetRequiredService())); 73 | 74 | return builder; 75 | } 76 | } -------------------------------------------------------------------------------- /TestRunner/Tests/ActivationTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading.Channels; 3 | using TestRunner.Monitoring; 4 | 5 | namespace TestRunner.Tests; 6 | 7 | public class ActivationTest 8 | { 9 | private readonly Activate _activate; 10 | private readonly ILogger _logger; 11 | 12 | public ActivationTest(Activate activate, ILogger logger) 13 | { 14 | _activate = activate; 15 | _logger = logger; 16 | } 17 | 18 | public async Task RunTest(int activationCount, int parallelism, CancellationToken cancel) 19 | { 20 | try 21 | { 22 | _logger.LogInformation( 23 | "Starting activation test with activation count = {ActivationCount}, parallelism = {Parallelism}", 24 | activationCount, parallelism); 25 | 26 | _logger.LogInformation("Preparing {ActivationCount} actor ids", activationCount); 27 | var actorIds = await PrepareActorIds(activationCount); 28 | 29 | var testDuration = await TestWorker(actorIds, parallelism, cancel); 30 | 31 | _logger.LogInformation( 32 | "Activation test completed, total activations = {TotalActivations}, duration = {TestDuration}, Throughput = {Throughput:F2} actors/s", 33 | activationCount, testDuration, activationCount / testDuration.TotalSeconds); 34 | } 35 | catch (Exception e) 36 | { 37 | _logger.LogError(e, "Activation test failed"); 38 | } 39 | } 40 | 41 | static async Task> PrepareActorIds(int count) 42 | { 43 | var ch = Channel.CreateBounded(count); 44 | 45 | for (int i = 0; i < count; i++) 46 | await ch.Writer.WriteAsync(Guid.NewGuid().ToString("N")); 47 | 48 | ch.Writer.Complete(); 49 | return ch.Reader; 50 | } 51 | 52 | async Task TestWorker(ChannelReader actorIds, int parallelism, CancellationToken cancel) 53 | { 54 | var overallStopwatch = new Stopwatch(); 55 | overallStopwatch.Start(); 56 | 57 | var tasks = Enumerable.Range(1, parallelism).Select(async _ => 58 | { 59 | var activationStopwatch = new Stopwatch(); 60 | 61 | await foreach (var actorId in actorIds.ReadAllAsync(cancel)) 62 | { 63 | try 64 | { 65 | activationStopwatch.Restart(); 66 | await _activate(actorId); 67 | 68 | TestMetrics.MessageLatency.Record(activationStopwatch.ElapsedTicks / (double)Stopwatch.Frequency); 69 | TestMetrics.MessageCount.Add(1); 70 | } 71 | catch (Exception e) 72 | { 73 | _logger.LogError(e, "Error during test"); 74 | TestMetrics.ErrorCount.Add(1); 75 | } 76 | } 77 | 78 | activationStopwatch.Stop(); 79 | }); 80 | 81 | await Task.WhenAll(tasks); 82 | 83 | overallStopwatch.Stop(); 84 | return overallStopwatch.Elapsed; 85 | } 86 | } -------------------------------------------------------------------------------- /kubernetes/deployment-orleans-sut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: orleans-sut 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: orleans-sut-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - patch 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: orleans-sut-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: orleans-sut-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: orleans-sut 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: orleans-sut 37 | labels: 38 | app: orleans-sut 39 | spec: 40 | replicas: 3 41 | selector: 42 | matchLabels: 43 | app: orleans-sut 44 | template: 45 | metadata: 46 | annotations: 47 | prometheus.io/scrape: 'false' 48 | labels: 49 | app: orleans-sut 50 | orleans/serviceId: actor-benchmark 51 | orleans/clusterId: actor-benchmark 52 | spec: 53 | serviceAccountName: orleans-sut 54 | containers: 55 | - name: main 56 | image: abimgregistry.azurecr.io/orleans-sut:latest 57 | imagePullPolicy: Always 58 | ports: 59 | - containerPort: 11111 60 | - containerPort: 30000 61 | - containerPort: 8001 62 | env: 63 | - name: Orleans__ClusteringStorage 64 | value: _storage_connection_string_ 65 | - name: SeqUrl 66 | value: http://my-seq.seq:5341 67 | - name: ORLEANS_SERVICE_ID 68 | valueFrom: 69 | fieldRef: 70 | fieldPath: metadata.labels['orleans/serviceId'] 71 | - name: ORLEANS_CLUSTER_ID 72 | valueFrom: 73 | fieldRef: 74 | fieldPath: metadata.labels['orleans/clusterId'] 75 | - name: POD_NAMESPACE 76 | valueFrom: 77 | fieldRef: 78 | fieldPath: metadata.namespace 79 | - name: POD_NAME 80 | valueFrom: 81 | fieldRef: 82 | fieldPath: metadata.name 83 | - name: POD_IP 84 | valueFrom: 85 | fieldRef: 86 | fieldPath: status.podIP 87 | - name: DOTNET_SHUTDOWNTIMEOUTSECONDS 88 | value: "120" 89 | terminationGracePeriodSeconds: 180 90 | affinity: 91 | nodeAffinity: 92 | requiredDuringSchedulingIgnoredDuringExecution: 93 | nodeSelectorTerms: 94 | - matchExpressions: 95 | - key: test-role 96 | operator: In 97 | values: 98 | - sut 99 | podAntiAffinity: 100 | requiredDuringSchedulingIgnoredDuringExecution: 101 | - topologyKey: "kubernetes.io/hostname" 102 | labelSelector: 103 | matchExpressions: 104 | - key: app 105 | operator: In 106 | values: 107 | - orleans-sut 108 | -------------------------------------------------------------------------------- /TestRunner/Tests/MessagingTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using TestRunner.Monitoring; 3 | 4 | namespace TestRunner.Tests; 5 | 6 | public class MessagingTest 7 | { 8 | readonly Activate _activate; 9 | readonly Ping _ping; 10 | readonly ILogger _logger; 11 | 12 | public MessagingTest(Activate activate, Ping ping, ILogger logger) 13 | { 14 | _activate = activate; 15 | _ping = ping; 16 | _logger = logger; 17 | } 18 | 19 | public async Task RunTest(int parallelism, int durationInSeconds, CancellationToken cancel) 20 | { 21 | try 22 | { 23 | _logger.LogInformation("Starting messaging test with parallelism = {Parallelism}, duration = {Duration}s", parallelism, 24 | durationInSeconds); 25 | 26 | var actorIds = PrepareActorIds(parallelism); 27 | 28 | _logger.LogInformation("Activating {Parallelism} actors", parallelism); 29 | var handles = await ActivateActors(actorIds); 30 | 31 | _logger.LogInformation("Starting the messaging test"); 32 | var cts = CancellationTokenSource.CreateLinkedTokenSource(cancel); 33 | cts.CancelAfter(TimeSpan.FromSeconds(durationInSeconds)); 34 | 35 | var (totalMessages, testDuration) = await TestWorker(handles, cts.Token); 36 | 37 | _logger.LogInformation("Messaging test completed, total messages = {TotalMessages}, duration = {TestDuration}, Throughput = {Throughput:F2} msg/s", 38 | totalMessages, testDuration, totalMessages / testDuration.TotalSeconds); 39 | } 40 | catch (Exception e) 41 | { 42 | _logger.LogError(e, "Messaging test failed"); 43 | } 44 | } 45 | 46 | string[] PrepareActorIds(int count) => 47 | Enumerable.Range(1, count).Select(i => $"{Environment.MachineName}-{i}").ToArray(); 48 | 49 | async Task ActivateActors(string[] actorIds) 50 | { 51 | var tasks = actorIds.Select(id => _activate(id)).ToArray(); 52 | await Task.WhenAll(tasks); 53 | return tasks.Select(t => t.Result).ToArray(); 54 | } 55 | 56 | async Task<(long TotalMessages, TimeSpan TestDuration)> TestWorker(object[] handles, CancellationToken cancel) 57 | { 58 | var totalMessages = 0L; 59 | var overallStopwatch = new Stopwatch(); 60 | overallStopwatch.Start(); 61 | 62 | var tasks = handles.Select(async handle => 63 | { 64 | var messageStopwatch = new Stopwatch(); 65 | while (!cancel.IsCancellationRequested) 66 | { 67 | try 68 | { 69 | messageStopwatch.Restart(); 70 | await _ping(handle, Guid.NewGuid().ToString("N")); 71 | 72 | TestMetrics.MessageLatency.Record(messageStopwatch.ElapsedTicks / (double)Stopwatch.Frequency); 73 | TestMetrics.MessageCount.Add(1); 74 | Interlocked.Increment(ref totalMessages); 75 | } 76 | catch (Exception e) 77 | { 78 | _logger.LogError(e, "Error during test"); 79 | TestMetrics.ErrorCount.Add(1); 80 | } 81 | } 82 | 83 | messageStopwatch.Stop(); 84 | }); 85 | 86 | await Task.WhenAll(tasks); 87 | 88 | overallStopwatch.Stop(); 89 | return (totalMessages, overallStopwatch.Elapsed); 90 | } 91 | } -------------------------------------------------------------------------------- /ProtoActorSut.Shared/ProtoActorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using Grpc.Net.Client; 3 | using Grpc.Net.Compression; 4 | using Proto; 5 | using Proto.Cluster; 6 | using Proto.Cluster.Consul; 7 | using Proto.Cluster.Kubernetes; 8 | using Proto.Cluster.Partition; 9 | using Proto.DependencyInjection; 10 | using Proto.Remote; 11 | using Proto.Remote.GrpcNet; 12 | 13 | namespace ProtoActorSut.Shared; 14 | 15 | public static class ProtoActorExtensions 16 | { 17 | public static WebApplicationBuilder AddProtoActor(this WebApplicationBuilder builder, 18 | params (string Kind, Props Props)[] kinds) 19 | { 20 | builder.Services.AddSingleton(provider => 21 | { 22 | var config = builder.Configuration.GetSection("ProtoActor"); 23 | 24 | Log.SetLoggerFactory(provider.GetRequiredService()); 25 | 26 | var actorSystemConfig = ActorSystemConfig 27 | .Setup() 28 | .WithDeadLetterThrottleCount(3) 29 | .WithDeadLetterThrottleInterval(TimeSpan.FromSeconds(1)); 30 | 31 | var system = new ActorSystem(actorSystemConfig); 32 | 33 | var (remoteConfig, clusterProvider) = 34 | ConfigureClustering(config, useKubernetes: !builder.Environment.IsDevelopment()); 35 | 36 | var clusterConfig = ClusterConfig 37 | .Setup(config["ClusterName"], clusterProvider, new PartitionIdentityLookup()); 38 | 39 | foreach (var kind in kinds) 40 | { 41 | clusterConfig = clusterConfig.WithClusterKind(kind.Kind, kind.Props); 42 | } 43 | 44 | system 45 | .WithServiceProvider(provider) 46 | .WithRemote(remoteConfig) 47 | .WithCluster(clusterConfig) 48 | .Cluster(); 49 | 50 | return system; 51 | }); 52 | 53 | builder.Services.AddSingleton(provider => provider.GetRequiredService().Cluster()); 54 | builder.Services.AddHostedService(); 55 | 56 | return builder; 57 | } 58 | 59 | static (GrpcNetRemoteConfig, IClusterProvider) 60 | ConfigureClustering(IConfigurationSection config, bool useKubernetes) => 61 | useKubernetes ? ConfigureForKubernetes(config) : ConfigureForLocalhost(); 62 | 63 | static (GrpcNetRemoteConfig, IClusterProvider) ConfigureForKubernetes(IConfigurationSection config) 64 | { 65 | var clusterProvider = new KubernetesProvider(new KubernetesProviderConfig()); 66 | 67 | var remoteConfig = GrpcNetRemoteConfig 68 | .BindToAllInterfaces(config["AdvertisedHost"], config.GetValue("AdvertisedPort", 0)) 69 | .WithChannelOptions(new GrpcChannelOptions 70 | { 71 | CompressionProviders = new[] 72 | { 73 | new GzipCompressionProvider(CompressionLevel.Fastest) 74 | } 75 | } 76 | ) 77 | .WithProtoMessages(Contracts.ProtosReflection.Descriptor) 78 | .WithLogLevelForDeserializationErrors(LogLevel.Critical); 79 | 80 | return (remoteConfig, clusterProvider); 81 | } 82 | 83 | static (GrpcNetRemoteConfig, IClusterProvider) ConfigureForLocalhost() 84 | => (GrpcNetRemoteConfig.BindToLocalhost() 85 | .WithProtoMessages(Contracts.ProtosReflection.Descriptor) 86 | .WithLogLevelForDeserializationErrors(LogLevel.Critical), 87 | new ConsulProvider(new ConsulProviderConfig())); 88 | } -------------------------------------------------------------------------------- /kubernetes/deployment-test-runner-orleans.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: test-runner 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: test-runner-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - patch 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: RoleBinding 23 | metadata: 24 | name: test-runner-binding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: test-runner-role 29 | subjects: 30 | - kind: ServiceAccount 31 | name: test-runner 32 | --- 33 | apiVersion: apps/v1 34 | kind: Deployment 35 | metadata: 36 | name: test-runner 37 | labels: 38 | app: test-runner 39 | spec: 40 | replicas: 3 41 | selector: 42 | matchLabels: 43 | app: test-runner 44 | template: 45 | metadata: 46 | annotations: 47 | prometheus.io/scrape: 'true' 48 | prometheus.io/port: '5000' 49 | labels: 50 | app: test-runner 51 | orleans/serviceId: actor-benchmark 52 | orleans/clusterId: actor-benchmark 53 | spec: 54 | serviceAccountName: test-runner 55 | containers: 56 | - name: main 57 | image: abimgregistry.azurecr.io/test-runner:latest 58 | imagePullPolicy: Always 59 | env: 60 | - name: ActorFramework 61 | value: Orleans 62 | - name: Orleans__ClusteringStorage 63 | value: _storage_connection_string_ 64 | - name: Orleans__ClusterId 65 | value: actor-benchmark 66 | - name: Orleans__ServiceId 67 | value: actor-benchmark 68 | - name: ORLEANS_SERVICE_ID 69 | valueFrom: 70 | fieldRef: 71 | fieldPath: metadata.labels['orleans/serviceId'] 72 | - name: ORLEANS_CLUSTER_ID 73 | valueFrom: 74 | fieldRef: 75 | fieldPath: metadata.labels['orleans/clusterId'] 76 | - name: POD_NAMESPACE 77 | valueFrom: 78 | fieldRef: 79 | fieldPath: metadata.namespace 80 | - name: POD_NAME 81 | valueFrom: 82 | fieldRef: 83 | fieldPath: metadata.name 84 | - name: POD_IP 85 | valueFrom: 86 | fieldRef: 87 | fieldPath: status.podIP 88 | - name: DOTNET_SHUTDOWNTIMEOUTSECONDS 89 | value: "120" 90 | 91 | - name: ProtoActor__ClusterName 92 | value: actor-benchmark 93 | - name: ProtoActor__AdvertisedHost 94 | valueFrom: 95 | fieldRef: 96 | fieldPath: status.podIP 97 | - name: Bus__ServiceBusConnectionString 98 | value: _service_bus_connection_string_ 99 | - name: SeqUrl 100 | value: http://my-seq.seq:5341 101 | terminationGracePeriodSeconds: 180 102 | affinity: 103 | nodeAffinity: 104 | requiredDuringSchedulingIgnoredDuringExecution: 105 | nodeSelectorTerms: 106 | - matchExpressions: 107 | - key: test-role 108 | operator: In 109 | values: 110 | - runner 111 | podAntiAffinity: 112 | requiredDuringSchedulingIgnoredDuringExecution: 113 | - topologyKey: "kubernetes.io/hostname" 114 | labelSelector: 115 | matchExpressions: 116 | - key: app 117 | operator: In 118 | values: 119 | - test-runner 120 | -------------------------------------------------------------------------------- /ActorBenchmark.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunner", "TestRunner\TestRunner.csproj", "{B13BD579-44A3-435F-853C-DB9C2EF30786}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrleansSut.Server", "OrleansSut.Server\OrleansSut.Server.csproj", "{40E56AD3-7287-4728-A66C-747889B61C08}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrleansSut.Shared", "OrleansSut.Shared\OrleansSut.Shared.csproj", "{2D752B1E-69B4-440C-9811-AE4159258063}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunner.Contract", "TestRunner.Contract\TestRunner.Contract.csproj", "{457AC816-F15D-478E-8A6E-35DD33B62E12}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunner.Client", "TestRunner.Client\TestRunner.Client.csproj", "{86CC193C-D101-490A-AA6B-7A591F4BB9A3}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtoActorSut.Server", "ProtoActorSut.Server\ProtoActorSut.Server.csproj", "{FBA3FDF1-CF3F-4E03-A3B5-03DC44A6E309}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtoActorSut.Shared", "ProtoActorSut.Shared\ProtoActorSut.Shared.csproj", "{5D73FA2F-4FB8-41FD-B38A-3E00ADDAF7E4}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AkkaSut.Shared", "AkkaSut.Shared\AkkaSut.Shared.csproj", "{3FE3AEED-504E-4948-9BFB-85DCE8DB61C3}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AkkaSut.Server", "AkkaSut.Server\AkkaSut.Server.csproj", "{13525A3A-CBEA-43E2-B698-D0F855D6F42F}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DaprSut.Shared", "DaprSut.Shared\DaprSut.Shared.csproj", "{F341BDC0-47B9-4A4E-99DE-0085D2F5F519}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DaprSut.Server", "DaprSut.Server\DaprSut.Server.csproj", "{43BCB7F4-DAD6-4ABA-831C-38F2096167D0}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {B13BD579-44A3-435F-853C-DB9C2EF30786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {B13BD579-44A3-435F-853C-DB9C2EF30786}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {B13BD579-44A3-435F-853C-DB9C2EF30786}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {B13BD579-44A3-435F-853C-DB9C2EF30786}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {40E56AD3-7287-4728-A66C-747889B61C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {40E56AD3-7287-4728-A66C-747889B61C08}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {40E56AD3-7287-4728-A66C-747889B61C08}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {40E56AD3-7287-4728-A66C-747889B61C08}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {2D752B1E-69B4-440C-9811-AE4159258063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {2D752B1E-69B4-440C-9811-AE4159258063}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {2D752B1E-69B4-440C-9811-AE4159258063}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {2D752B1E-69B4-440C-9811-AE4159258063}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {457AC816-F15D-478E-8A6E-35DD33B62E12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {457AC816-F15D-478E-8A6E-35DD33B62E12}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {457AC816-F15D-478E-8A6E-35DD33B62E12}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {457AC816-F15D-478E-8A6E-35DD33B62E12}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {86CC193C-D101-490A-AA6B-7A591F4BB9A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {86CC193C-D101-490A-AA6B-7A591F4BB9A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {86CC193C-D101-490A-AA6B-7A591F4BB9A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {86CC193C-D101-490A-AA6B-7A591F4BB9A3}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {FBA3FDF1-CF3F-4E03-A3B5-03DC44A6E309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {FBA3FDF1-CF3F-4E03-A3B5-03DC44A6E309}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {FBA3FDF1-CF3F-4E03-A3B5-03DC44A6E309}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {FBA3FDF1-CF3F-4E03-A3B5-03DC44A6E309}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {5D73FA2F-4FB8-41FD-B38A-3E00ADDAF7E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {5D73FA2F-4FB8-41FD-B38A-3E00ADDAF7E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {5D73FA2F-4FB8-41FD-B38A-3E00ADDAF7E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {5D73FA2F-4FB8-41FD-B38A-3E00ADDAF7E4}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {3FE3AEED-504E-4948-9BFB-85DCE8DB61C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {3FE3AEED-504E-4948-9BFB-85DCE8DB61C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {3FE3AEED-504E-4948-9BFB-85DCE8DB61C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {3FE3AEED-504E-4948-9BFB-85DCE8DB61C3}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {13525A3A-CBEA-43E2-B698-D0F855D6F42F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {13525A3A-CBEA-43E2-B698-D0F855D6F42F}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {13525A3A-CBEA-43E2-B698-D0F855D6F42F}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {13525A3A-CBEA-43E2-B698-D0F855D6F42F}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {F341BDC0-47B9-4A4E-99DE-0085D2F5F519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {F341BDC0-47B9-4A4E-99DE-0085D2F5F519}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {F341BDC0-47B9-4A4E-99DE-0085D2F5F519}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {F341BDC0-47B9-4A4E-99DE-0085D2F5F519}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {43BCB7F4-DAD6-4ABA-831C-38F2096167D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {43BCB7F4-DAD6-4ABA-831C-38F2096167D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {43BCB7F4-DAD6-4ABA-831C-38F2096167D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {43BCB7F4-DAD6-4ABA-831C-38F2096167D0}.Release|Any CPU.Build.0 = Release|Any CPU 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET virtual actor frameworks benchmark 2 | 3 | This benchmark compares messaging throughput and activation time of popular .NET virtual actor frameworks: Orleans, Proto.Actor, Akka.Net, Dapr. 4 | 5 | ## Environment setup 6 | 7 | ### Local tools 8 | 9 | 1. Azure CLI 10 | 2. Docker 11 | 3. helm 12 | 4. Dapr CLI and local env (install, then run with `dapr run --app-id myapp --app-port 5071 --dapr-http-port 3500`) 13 | 14 | ### Azure 15 | 16 | (You can use ARM template in the [azure](azure) directory as a reference) 17 | 18 | Create following Azure resources: 19 | 1. Azure Storage Account - connection string is required for Orleans clustering 20 | 2. Azure Service Bus (Standard) - used to for communication between Test Runner and Test Runner client 21 | 3. Azure Container Registry - it will store Docker images 22 | 4. Azure Kubernetes Service - environment for running the tests 23 | 1. 2 node system pool, B2ms, label: `test-role=management` 24 | 2. 3 node user pool, D4, label: `test-role=sut` 25 | 3. 3 node user pool, D4, label: `test-role=runner` 26 | 4. RBAC enabled 27 | 5. No availability zones 28 | 6. No Azure monitoring 29 | 7. Be sure to connect it with the ACR upon creation 30 | 31 | Get credentials for AKS, e.g. 32 | 33 | ```shell 34 | az aks list -o table 35 | 36 | # Name Location ResourceGroup KubernetesVersion ProvisioningState Fqdn 37 | # ------ ----------- --------------- ------------------- ------------------- --------------------------------------------- 38 | # ab-k8s northeurope ActorBenchmark 1.22.6 Succeeded ab-k8s-dns-9a630584.hcp.northeurope.azmk8s.io 39 | 40 | az aks get-credentials -n ab-k8s -g ActorBenchmark 41 | ``` 42 | 43 | ## Supporting services in k8s 44 | 45 | ### Seq 46 | Used for central logging. 47 | 48 | ```shell 49 | helm repo add datalust https://helm.datalust.co 50 | helm repo update 51 | helm install my-seq -f kubernetes/values-seq.yaml -n seq --create-namespace datalust/seq 52 | ``` 53 | 54 | You can connect to seq by forwarding a port, e.g.: 55 | ```shell 56 | kubectl port-forward service/my-seq -n seq 5341:80 57 | ``` 58 | 59 | Then open http://localhost:5241 60 | 61 | ### Prometheus 62 | 63 | ```shell 64 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 65 | helm repo update 66 | helm install -n prometheus --create-namespace -f kubernetes/values-prometheus.yaml my-prometheus prometheus-community/prometheus 67 | ``` 68 | 69 | ### Grafana 70 | 71 | ```shell 72 | helm repo add grafana https://grafana.github.io/helm-charts 73 | helm repo update 74 | helm install -n grafana --create-namespace -f kubernetes/values-grafana.yaml my-grafana grafana/grafana 75 | ``` 76 | 77 | You can connect to Grafana by forwarding a port, e.g.: 78 | 79 | ```shell 80 | kubectl port-forward service/my-grafana -n grafana 3000:80 81 | ``` 82 | 83 | Then open http://localhost:3000 84 | 85 | ## Deploy benchmark to k8s 86 | 87 | This walkthrough assumes the container registry is named `abimgregistry.azurecr.io`. If you're using a different name, update the `docker/image-*.sh` and `kubernetes/deployment-*.yaml` scripts. 88 | 89 | 90 | ### Prepare the images 91 | 92 | Log into the image registry on your machine, e.g.: 93 | 94 | ```shell 95 | az acr list -o table 96 | 97 | # NAME RESOURCE GROUP LOCATION SKU LOGIN SERVER CREATION DATE ADMIN ENABLED 98 | # ------------- ---------------- ----------- ----- ------------------------ -------------------- --------------- 99 | # abimgregistry ActorBenchmark northeurope Basic abimgregistry.azurecr.io 2022-03-25T07:55:04Z False 100 | 101 | az acr login -n abimgregistry 102 | ``` 103 | 104 | Run these scripts to build and push images. 105 | 106 | ```shell 107 | cd docker 108 | ./image-orleans-sut.sh 109 | ./image-proto-actor-sut.sh 110 | ./image-akka-sut.sh 111 | ./image-dapr-sut.sh 112 | ./image-test-runner.sh 113 | ``` 114 | 115 | ### Prepare namespace 116 | 117 | ```shell 118 | kubectl create namespace benchmark 119 | ``` 120 | 121 | ### Orleans tests 122 | 123 | #### Orleans SUT 124 | 125 | Replace Azure storage connection string in `kubernetes/deployment-orleans-sut.yaml` 126 | 127 | ```shell 128 | kubectl apply -n benchmark -f kubernetes/deployment-orleans-sut.yaml 129 | ``` 130 | 131 | #### Test runner - for Orleans test 132 | 133 | *Note: Only one configuration of test runner can be deployed at a time.* 134 | 135 | Replace Azure Service bus connection string and Azure storage connection string in `kubernetes/deployment-test-runner-orleans.yaml` 136 | 137 | ```shell 138 | kubectl apply -n benchmark -f kubernetes/deployment-test-runner-orleans.yaml 139 | ``` 140 | 141 | ### Proto.Actor tests 142 | 143 | #### Proto.Actor SUT 144 | 145 | ```shell 146 | kubectl apply -n benchmark -f kubernetes/deployment-proto-actor-sut.yaml 147 | ``` 148 | 149 | #### Test runner - for Proto.Actor test 150 | 151 | *Note: Only one configuration of test runner can be deployed at a time.* 152 | 153 | Replace Azure Service bus connection string string in `kubernetes/deployment-test-runner-proto-actor.yaml` 154 | 155 | ```shell 156 | kubectl apply -n benchmark -f kubernetes/deployment-test-runner-proto-actor-raw.yaml 157 | ``` 158 | 159 | ### Akka tests 160 | 161 | #### Lighthouse 162 | 163 | Akka.net Lighthouse is used as seed node provider. 164 | 165 | ```shell 166 | kubectl apply -n benchmark -f kubernetes/statefulset-lighthouse.yml 167 | ``` 168 | 169 | #### Akka SUT 170 | 171 | ```shell 172 | kubectl apply -n benchmark -f kubernetes/deployment-akka-sut.yaml 173 | ``` 174 | 175 | #### Test runner - for Akka test 176 | 177 | *Note: Only one configuration of test runner can be deployed at a time.* 178 | 179 | Replace Azure Service bus connection string in `kubernetes/deployment-test-runner-akka.yaml` 180 | 181 | ```shell 182 | kubectl apply -n benchmark -f kubernetes/deployment-test-runner-akka.yaml 183 | ``` 184 | 185 | ### Dapr tests 186 | 187 | #### Dapr environment 188 | 189 | You'll need a Redis deployment 190 | ```shell 191 | helm repo add bitnami https://charts.bitnami.com/bitnami 192 | helm repo update 193 | helm install -n redis --create-namespace -f kubernetes/values-redis.yaml my-redis bitnami/redis 194 | ``` 195 | 196 | You'll also need to install Dapr in the cluster 197 | ```shell 198 | helm repo add dapr https://dapr.github.io/helm-charts/ 199 | helm repo update 200 | helm install -n dapr --create-namespace -f kubernetes/values-dapr.yaml my-dapr dapr/dapr 201 | kubectl apply -n benchmark -f kubernetes/dapr-state-store.yaml 202 | kubectl apply -n benchmark -f kubernetes/dapr-app-config.yaml 203 | ``` 204 | 205 | #### Dapr SUT 206 | 207 | ```shell 208 | kubectl apply -n benchmark -f kubernetes/deployment-dapr-sut.yaml 209 | ``` 210 | 211 | #### Test runner - for Dapr test 212 | 213 | *Note: Only one configuration of test runner can be deployed at a time.* 214 | 215 | Replace Azure Service bus connection string in `kubernetes/deployment-test-runner-akka.yaml` 216 | 217 | ```shell 218 | kubectl apply -n benchmark -f kubernetes/deployment-test-runner-dapr.yaml 219 | ``` 220 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/rider,macos,aspnetcore,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=rider,macos,aspnetcore,visualstudiocode 3 | 4 | ### ASPNETCore ### 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # Visual Studio code coverage results 118 | *.coverage 119 | *.coveragexml 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | # Comment the next line if you want to checkin your web deploy settings 153 | # but database connection strings (with potential passwords) will be unencrypted 154 | *.pubxml 155 | *.publishproj 156 | 157 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 158 | # checkin your Azure Web App publish settings, but sensitive information contained 159 | # in these scripts will be unencrypted 160 | PublishScripts/ 161 | 162 | # NuGet Packages 163 | *.nupkg 164 | # The packages folder can be ignored because of Package Restore 165 | **/packages/* 166 | # except build/, which is used as an MSBuild target. 167 | !**/packages/build/ 168 | # Uncomment if necessary however generally it will be regenerated when needed 169 | #!**/packages/repositories.config 170 | # NuGet v3's project.json files produces more ignoreable files 171 | *.nuget.props 172 | *.nuget.targets 173 | 174 | # Microsoft Azure Build Output 175 | csx/ 176 | *.build.csdef 177 | 178 | # Microsoft Azure Emulator 179 | ecf/ 180 | rcf/ 181 | 182 | # Windows Store app package directories and files 183 | AppPackages/ 184 | BundleArtifacts/ 185 | Package.StoreAssociation.xml 186 | _pkginfo.txt 187 | 188 | # Visual Studio cache files 189 | # files ending in .cache can be ignored 190 | *.[Cc]ache 191 | # but keep track of directories ending in .cache 192 | !*.[Cc]ache/ 193 | 194 | # Others 195 | ClientBin/ 196 | ~$* 197 | *~ 198 | *.dbmdl 199 | *.dbproj.schemaview 200 | *.jfm 201 | *.pfx 202 | *.publishsettings 203 | node_modules/ 204 | orleans.codegen.cs 205 | 206 | # Since there are multiple workflows, uncomment next line to ignore bower_components 207 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 208 | #bower_components/ 209 | 210 | # RIA/Silverlight projects 211 | Generated_Code/ 212 | 213 | # Backup & report files from converting an old project file 214 | # to a newer Visual Studio version. Backup files are not needed, 215 | # because we have git ;-) 216 | _UpgradeReport_Files/ 217 | Backup*/ 218 | UpgradeLog*.XML 219 | UpgradeLog*.htm 220 | 221 | # SQL Server files 222 | *.mdf 223 | *.ldf 224 | 225 | # Business Intelligence projects 226 | *.rdl.data 227 | *.bim.layout 228 | *.bim_*.settings 229 | 230 | # Microsoft Fakes 231 | FakesAssemblies/ 232 | 233 | # GhostDoc plugin setting file 234 | *.GhostDoc.xml 235 | 236 | # Node.js Tools for Visual Studio 237 | .ntvs_analysis.dat 238 | 239 | # Visual Studio 6 build log 240 | *.plg 241 | 242 | # Visual Studio 6 workspace options file 243 | *.opt 244 | 245 | # Visual Studio LightSwitch build output 246 | **/*.HTMLClient/GeneratedArtifacts 247 | **/*.DesktopClient/GeneratedArtifacts 248 | **/*.DesktopClient/ModelManifest.xml 249 | **/*.Server/GeneratedArtifacts 250 | **/*.Server/ModelManifest.xml 251 | _Pvt_Extensions 252 | 253 | # Paket dependency manager 254 | .paket/paket.exe 255 | paket-files/ 256 | 257 | # FAKE - F# Make 258 | .fake/ 259 | 260 | # JetBrains Rider 261 | .idea/ 262 | *.sln.iml 263 | 264 | # CodeRush 265 | .cr/ 266 | 267 | # Python Tools for Visual Studio (PTVS) 268 | __pycache__/ 269 | *.pyc 270 | 271 | # Cake - Uncomment if you are using it 272 | # tools/ 273 | 274 | ### macOS ### 275 | # General 276 | .DS_Store 277 | .AppleDouble 278 | .LSOverride 279 | 280 | # Icon must end with two \r 281 | Icon 282 | 283 | # Thumbnails 284 | ._* 285 | 286 | # Files that might appear in the root of a volume 287 | .DocumentRevisions-V100 288 | .fseventsd 289 | .Spotlight-V100 290 | .TemporaryItems 291 | .Trashes 292 | .VolumeIcon.icns 293 | .com.apple.timemachine.donotpresent 294 | 295 | # Directories potentially created on remote AFP share 296 | .AppleDB 297 | .AppleDesktop 298 | Network Trash Folder 299 | Temporary Items 300 | .apdisk 301 | 302 | ### Rider ### 303 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 304 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 305 | 306 | # User-specific stuff 307 | .idea/**/workspace.xml 308 | .idea/**/tasks.xml 309 | .idea/**/usage.statistics.xml 310 | .idea/**/dictionaries 311 | .idea/**/shelf 312 | 313 | # Generated files 314 | .idea/**/contentModel.xml 315 | 316 | # Sensitive or high-churn files 317 | .idea/**/dataSources/ 318 | .idea/**/dataSources.ids 319 | .idea/**/dataSources.local.xml 320 | .idea/**/sqlDataSources.xml 321 | .idea/**/dynamic.xml 322 | .idea/**/uiDesigner.xml 323 | .idea/**/dbnavigator.xml 324 | 325 | # Gradle 326 | .idea/**/gradle.xml 327 | .idea/**/libraries 328 | 329 | # Gradle and Maven with auto-import 330 | # When using Gradle or Maven with auto-import, you should exclude module files, 331 | # since they will be recreated, and may cause churn. Uncomment if using 332 | # auto-import. 333 | # .idea/modules.xml 334 | # .idea/*.iml 335 | # .idea/modules 336 | # *.iml 337 | # *.ipr 338 | 339 | # CMake 340 | cmake-build-*/ 341 | 342 | # Mongo Explorer plugin 343 | .idea/**/mongoSettings.xml 344 | 345 | # File-based project format 346 | *.iws 347 | 348 | # IntelliJ 349 | out/ 350 | 351 | # mpeltonen/sbt-idea plugin 352 | .idea_modules/ 353 | 354 | # JIRA plugin 355 | atlassian-ide-plugin.xml 356 | 357 | # Cursive Clojure plugin 358 | .idea/replstate.xml 359 | 360 | # Crashlytics plugin (for Android Studio and IntelliJ) 361 | com_crashlytics_export_strings.xml 362 | crashlytics.properties 363 | crashlytics-build.properties 364 | fabric.properties 365 | 366 | # Editor-based Rest Client 367 | .idea/httpRequests 368 | 369 | # Android studio 3.1+ serialized cache file 370 | .idea/caches/build_file_checksums.ser 371 | 372 | ### VisualStudioCode ### 373 | .vscode/* 374 | !.vscode/settings.json 375 | !.vscode/tasks.json 376 | !.vscode/launch.json 377 | !.vscode/extensions.json 378 | 379 | ### VisualStudioCode Patch ### 380 | # Ignore all local history of files 381 | .history 382 | 383 | # End of https://www.gitignore.io/api/rider,macos,aspnetcore,visualstudiocode -------------------------------------------------------------------------------- /azure/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "namespaces_aborleans_name": { 6 | "defaultValue": "aborleans", 7 | "type": "String" 8 | }, 9 | "storageAccounts_aborleans_name": { 10 | "defaultValue": "aborleans", 11 | "type": "String" 12 | }, 13 | "managedClusters_ab_k8s_name": { 14 | "defaultValue": "ab-k8s", 15 | "type": "String" 16 | }, 17 | "registries_abimgregistry_name": { 18 | "defaultValue": "abimgregistry", 19 | "type": "String" 20 | }, 21 | "publicIPAddresses_fede5ea6_3328_4298_8b2c_69a6a7848948_externalid": { 22 | "defaultValue": "", 23 | "type": "String" 24 | }, 25 | "userAssignedIdentities_ab_k8s_agentpool_externalid": { 26 | "defaultValue": "", 27 | "type": "String" 28 | } 29 | }, 30 | "variables": {}, 31 | "resources": [ 32 | { 33 | "type": "Microsoft.ContainerRegistry/registries", 34 | "apiVersion": "2021-12-01-preview", 35 | "name": "[parameters('registries_abimgregistry_name')]", 36 | "location": "northeurope", 37 | "sku": { 38 | "name": "Basic", 39 | "tier": "Basic" 40 | }, 41 | "properties": { 42 | "adminUserEnabled": false, 43 | "policies": { 44 | "quarantinePolicy": { 45 | "status": "disabled" 46 | }, 47 | "trustPolicy": { 48 | "type": "Notary", 49 | "status": "disabled" 50 | }, 51 | "retentionPolicy": { 52 | "days": 7, 53 | "status": "disabled" 54 | }, 55 | "exportPolicy": { 56 | "status": "enabled" 57 | } 58 | }, 59 | "encryption": { 60 | "status": "disabled" 61 | }, 62 | "dataEndpointEnabled": false, 63 | "publicNetworkAccess": "Enabled", 64 | "networkRuleBypassOptions": "AzureServices", 65 | "zoneRedundancy": "Disabled", 66 | "anonymousPullEnabled": false 67 | } 68 | }, 69 | { 70 | "type": "Microsoft.ContainerService/managedClusters", 71 | "apiVersion": "2022-02-02-preview", 72 | "name": "[parameters('managedClusters_ab_k8s_name')]", 73 | "location": "northeurope", 74 | "sku": { 75 | "name": "Basic", 76 | "tier": "Free" 77 | }, 78 | "identity": { 79 | "type": "SystemAssigned" 80 | }, 81 | "properties": { 82 | "kubernetesVersion": "1.22.6", 83 | "dnsPrefix": "[concat(parameters('managedClusters_ab_k8s_name'), '-dns')]", 84 | "agentPoolProfiles": [ 85 | { 86 | "name": "management", 87 | "count": 2, 88 | "vmSize": "Standard_B2ms", 89 | "osDiskSizeGB": 128, 90 | "osDiskType": "Managed", 91 | "kubeletDiskType": "OS", 92 | "maxPods": 110, 93 | "type": "VirtualMachineScaleSets", 94 | "enableAutoScaling": false, 95 | "scaleDownMode": "Delete", 96 | "powerState": { 97 | "code": "Running" 98 | }, 99 | "orchestratorVersion": "1.22.6", 100 | "enableNodePublicIP": false, 101 | "nodeLabels": { 102 | "test-role": "management" 103 | }, 104 | "mode": "System", 105 | "osType": "Linux", 106 | "osSKU": "Ubuntu", 107 | "upgradeSettings": {}, 108 | "enableFIPS": false 109 | }, 110 | { 111 | "name": "runner", 112 | "count": 3, 113 | "vmSize": "Standard_D4s_v4", 114 | "osDiskSizeGB": 128, 115 | "osDiskType": "Managed", 116 | "kubeletDiskType": "OS", 117 | "maxPods": 110, 118 | "type": "VirtualMachineScaleSets", 119 | "enableAutoScaling": false, 120 | "scaleDownMode": "Delete", 121 | "powerState": { 122 | "code": "Running" 123 | }, 124 | "orchestratorVersion": "1.22.6", 125 | "enableNodePublicIP": false, 126 | "nodeLabels": { 127 | "test-role": "runner" 128 | }, 129 | "mode": "User", 130 | "osType": "Linux", 131 | "osSKU": "Ubuntu", 132 | "upgradeSettings": {}, 133 | "enableFIPS": false 134 | }, 135 | { 136 | "name": "sut", 137 | "count": 3, 138 | "vmSize": "Standard_D4s_v5", 139 | "osDiskSizeGB": 128, 140 | "osDiskType": "Managed", 141 | "kubeletDiskType": "OS", 142 | "maxPods": 110, 143 | "type": "VirtualMachineScaleSets", 144 | "enableAutoScaling": false, 145 | "scaleDownMode": "Delete", 146 | "powerState": { 147 | "code": "Running" 148 | }, 149 | "orchestratorVersion": "1.22.6", 150 | "enableNodePublicIP": false, 151 | "nodeLabels": { 152 | "test-role": "sut" 153 | }, 154 | "mode": "User", 155 | "osType": "Linux", 156 | "osSKU": "Ubuntu", 157 | "upgradeSettings": {}, 158 | "enableFIPS": false 159 | } 160 | ], 161 | "servicePrincipalProfile": { 162 | "clientId": "msi" 163 | }, 164 | "addonProfiles": { 165 | "azureKeyvaultSecretsProvider": { 166 | "enabled": false 167 | }, 168 | "azurepolicy": { 169 | "enabled": false 170 | }, 171 | "httpApplicationRouting": { 172 | "enabled": false 173 | } 174 | }, 175 | "nodeResourceGroup": "[concat('MC_ActorBenchmark_', parameters('managedClusters_ab_k8s_name'), '_northeurope')]", 176 | "enableRBAC": true, 177 | "networkProfile": { 178 | "networkPlugin": "kubenet", 179 | "loadBalancerSku": "Standard", 180 | "loadBalancerProfile": { 181 | "managedOutboundIPs": { 182 | "count": 1 183 | }, 184 | "effectiveOutboundIPs": [ 185 | { 186 | "id": "[parameters('publicIPAddresses_fede5ea6_3328_4298_8b2c_69a6a7848948_externalid')]" 187 | } 188 | ] 189 | }, 190 | "podCidr": "10.244.0.0/16", 191 | "serviceCidr": "10.0.0.0/16", 192 | "dnsServiceIP": "10.0.0.10", 193 | "dockerBridgeCidr": "172.17.0.1/16", 194 | "outboundType": "loadBalancer" 195 | }, 196 | "apiServerAccessProfile": { 197 | "enablePrivateCluster": false 198 | }, 199 | "identityProfile": { 200 | "kubeletidentity": { 201 | "resourceId": "[parameters('userAssignedIdentities_ab_k8s_agentpool_externalid')]", 202 | "clientId": "10508a22-215e-49f7-b45f-d5f2a384aed5", 203 | "objectId": "2e08feca-6696-414a-96c8-ccb99200cf5e" 204 | } 205 | }, 206 | "securityProfile": {}, 207 | "oidcIssuerProfile": { 208 | "enabled": false 209 | } 210 | } 211 | }, 212 | { 213 | "type": "Microsoft.ServiceBus/namespaces", 214 | "apiVersion": "2021-11-01", 215 | "name": "[parameters('namespaces_aborleans_name')]", 216 | "location": "West Europe", 217 | "sku": { 218 | "name": "Standard", 219 | "tier": "Standard" 220 | }, 221 | "properties": { 222 | "disableLocalAuth": false, 223 | "zoneRedundant": false 224 | } 225 | }, 226 | { 227 | "type": "Microsoft.Storage/storageAccounts", 228 | "apiVersion": "2021-09-01", 229 | "name": "[parameters('storageAccounts_aborleans_name')]", 230 | "location": "northeurope", 231 | "sku": { 232 | "name": "Standard_LRS", 233 | "tier": "Standard" 234 | }, 235 | "kind": "StorageV2", 236 | "properties": { 237 | "defaultToOAuthAuthentication": false, 238 | "allowCrossTenantReplication": true, 239 | "minimumTlsVersion": "TLS1_2", 240 | "allowBlobPublicAccess": true, 241 | "allowSharedKeyAccess": true, 242 | "networkAcls": { 243 | "bypass": "AzureServices", 244 | "virtualNetworkRules": [], 245 | "ipRules": [], 246 | "defaultAction": "Allow" 247 | }, 248 | "supportsHttpsTrafficOnly": true, 249 | "encryption": { 250 | "requireInfrastructureEncryption": false, 251 | "services": { 252 | "file": { 253 | "keyType": "Account", 254 | "enabled": true 255 | }, 256 | "blob": { 257 | "keyType": "Account", 258 | "enabled": true 259 | } 260 | }, 261 | "keySource": "Microsoft.Storage" 262 | }, 263 | "accessTier": "Hot" 264 | } 265 | }, 266 | { 267 | "type": "Microsoft.ContainerService/managedClusters/agentPools", 268 | "apiVersion": "2022-02-02-preview", 269 | "name": "[concat(parameters('managedClusters_ab_k8s_name'), '/management')]", 270 | "dependsOn": [ 271 | "[resourceId('Microsoft.ContainerService/managedClusters', parameters('managedClusters_ab_k8s_name'))]" 272 | ], 273 | "properties": { 274 | "count": 2, 275 | "vmSize": "Standard_B2ms", 276 | "osDiskSizeGB": 128, 277 | "osDiskType": "Managed", 278 | "kubeletDiskType": "OS", 279 | "maxPods": 110, 280 | "type": "VirtualMachineScaleSets", 281 | "enableAutoScaling": false, 282 | "scaleDownMode": "Delete", 283 | "powerState": { 284 | "code": "Running" 285 | }, 286 | "orchestratorVersion": "1.22.6", 287 | "enableNodePublicIP": false, 288 | "nodeLabels": { 289 | "test-role": "management" 290 | }, 291 | "mode": "System", 292 | "osType": "Linux", 293 | "osSKU": "Ubuntu", 294 | "upgradeSettings": {}, 295 | "enableFIPS": false 296 | } 297 | }, 298 | { 299 | "type": "Microsoft.ContainerService/managedClusters/agentPools", 300 | "apiVersion": "2022-02-02-preview", 301 | "name": "[concat(parameters('managedClusters_ab_k8s_name'), '/runner')]", 302 | "dependsOn": [ 303 | "[resourceId('Microsoft.ContainerService/managedClusters', parameters('managedClusters_ab_k8s_name'))]" 304 | ], 305 | "properties": { 306 | "count": 3, 307 | "vmSize": "Standard_D4s_v4", 308 | "osDiskSizeGB": 128, 309 | "osDiskType": "Managed", 310 | "kubeletDiskType": "OS", 311 | "maxPods": 110, 312 | "type": "VirtualMachineScaleSets", 313 | "enableAutoScaling": false, 314 | "scaleDownMode": "Delete", 315 | "powerState": { 316 | "code": "Running" 317 | }, 318 | "orchestratorVersion": "1.22.6", 319 | "enableNodePublicIP": false, 320 | "nodeLabels": { 321 | "test-role": "runner" 322 | }, 323 | "mode": "User", 324 | "osType": "Linux", 325 | "osSKU": "Ubuntu", 326 | "upgradeSettings": {}, 327 | "enableFIPS": false 328 | } 329 | }, 330 | { 331 | "type": "Microsoft.ContainerService/managedClusters/agentPools", 332 | "apiVersion": "2022-02-02-preview", 333 | "name": "[concat(parameters('managedClusters_ab_k8s_name'), '/sut')]", 334 | "dependsOn": [ 335 | "[resourceId('Microsoft.ContainerService/managedClusters', parameters('managedClusters_ab_k8s_name'))]" 336 | ], 337 | "properties": { 338 | "count": 3, 339 | "vmSize": "Standard_D4s_v5", 340 | "osDiskSizeGB": 128, 341 | "osDiskType": "Managed", 342 | "kubeletDiskType": "OS", 343 | "maxPods": 110, 344 | "type": "VirtualMachineScaleSets", 345 | "enableAutoScaling": false, 346 | "scaleDownMode": "Delete", 347 | "powerState": { 348 | "code": "Running" 349 | }, 350 | "orchestratorVersion": "1.22.6", 351 | "enableNodePublicIP": false, 352 | "nodeLabels": { 353 | "test-role": "sut" 354 | }, 355 | "mode": "User", 356 | "osType": "Linux", 357 | "osSKU": "Ubuntu", 358 | "upgradeSettings": {}, 359 | "enableFIPS": false 360 | } 361 | }, 362 | { 363 | "type": "Microsoft.ServiceBus/namespaces/AuthorizationRules", 364 | "apiVersion": "2021-11-01", 365 | "name": "[concat(parameters('namespaces_aborleans_name'), '/RootManageSharedAccessKey')]", 366 | "location": "West Europe", 367 | "dependsOn": [ 368 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('namespaces_aborleans_name'))]" 369 | ], 370 | "properties": { 371 | "rights": [ 372 | "Listen", 373 | "Manage", 374 | "Send" 375 | ] 376 | } 377 | }, 378 | { 379 | "type": "Microsoft.ServiceBus/namespaces/networkRuleSets", 380 | "apiVersion": "2021-11-01", 381 | "name": "[concat(parameters('namespaces_aborleans_name'), '/default')]", 382 | "location": "West Europe", 383 | "dependsOn": [ 384 | "[resourceId('Microsoft.ServiceBus/namespaces', parameters('namespaces_aborleans_name'))]" 385 | ], 386 | "properties": { 387 | "publicNetworkAccess": "Enabled", 388 | "defaultAction": "Allow", 389 | "virtualNetworkRules": [], 390 | "ipRules": [] 391 | } 392 | }, 393 | { 394 | "type": "Microsoft.Storage/storageAccounts/blobServices", 395 | "apiVersion": "2021-09-01", 396 | "name": "[concat(parameters('storageAccounts_aborleans_name'), '/default')]", 397 | "dependsOn": [ 398 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_aborleans_name'))]" 399 | ], 400 | "sku": { 401 | "name": "Standard_LRS", 402 | "tier": "Standard" 403 | }, 404 | "properties": { 405 | "cors": { 406 | "corsRules": [] 407 | }, 408 | "deleteRetentionPolicy": { 409 | "enabled": true, 410 | "days": 7 411 | }, 412 | "isVersioningEnabled": false, 413 | "changeFeed": { 414 | "enabled": false 415 | }, 416 | "restorePolicy": { 417 | "enabled": false 418 | }, 419 | "containerDeleteRetentionPolicy": { 420 | "enabled": true, 421 | "days": 7 422 | } 423 | } 424 | }, 425 | { 426 | "type": "Microsoft.Storage/storageAccounts/fileServices", 427 | "apiVersion": "2021-09-01", 428 | "name": "[concat(parameters('storageAccounts_aborleans_name'), '/default')]", 429 | "dependsOn": [ 430 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_aborleans_name'))]" 431 | ], 432 | "sku": { 433 | "name": "Standard_LRS", 434 | "tier": "Standard" 435 | }, 436 | "properties": { 437 | "protocolSettings": { 438 | "smb": {} 439 | }, 440 | "cors": { 441 | "corsRules": [] 442 | }, 443 | "shareDeleteRetentionPolicy": { 444 | "enabled": true, 445 | "days": 7 446 | } 447 | } 448 | }, 449 | { 450 | "type": "Microsoft.Storage/storageAccounts/queueServices", 451 | "apiVersion": "2021-09-01", 452 | "name": "[concat(parameters('storageAccounts_aborleans_name'), '/default')]", 453 | "dependsOn": [ 454 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_aborleans_name'))]" 455 | ], 456 | "properties": { 457 | "cors": { 458 | "corsRules": [] 459 | } 460 | } 461 | }, 462 | { 463 | "type": "Microsoft.Storage/storageAccounts/tableServices", 464 | "apiVersion": "2021-09-01", 465 | "name": "[concat(parameters('storageAccounts_aborleans_name'), '/default')]", 466 | "dependsOn": [ 467 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_aborleans_name'))]" 468 | ], 469 | "properties": { 470 | "cors": { 471 | "corsRules": [] 472 | } 473 | } 474 | }, 475 | { 476 | "type": "Microsoft.Storage/storageAccounts/tableServices/tables", 477 | "apiVersion": "2021-09-01", 478 | "name": "[concat(parameters('storageAccounts_aborleans_name'), '/default/OrleansSiloInstances')]", 479 | "dependsOn": [ 480 | "[resourceId('Microsoft.Storage/storageAccounts/tableServices', parameters('storageAccounts_aborleans_name'), 'default')]", 481 | "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_aborleans_name'))]" 482 | ], 483 | "properties": {} 484 | } 485 | ] 486 | } --------------------------------------------------------------------------------