├── logo.png ├── ManagedCode.Orleans.Balancer.Tests ├── Cluster │ ├── Grains │ │ ├── ITestGrain.cs │ │ ├── ITestGrainInt.cs │ │ ├── ITestGrainString.cs │ │ ├── IDoGrain.cs │ │ ├── TestGrainInt.cs │ │ ├── TestGrain.cs │ │ └── TestGrainString.cs │ └── TestSiloConfigurations.cs ├── BaseTests.cs ├── ManagedCode.Orleans.Balancer.Tests.csproj ├── LocalDeactivatorTests.cs ├── BalancerGrainTests.cs └── SiloTests.cs ├── ManagedCode.Orleans.Balancer ├── Attributes │ ├── DeactivationPriority.cs │ └── CanBeDeactivatedAttribute.cs ├── Abstractions │ ├── IBalancerGrain.cs │ └── ILocalDeactivatorGrain.cs ├── OrleansBalancerOptions.cs ├── ManagedCode.Orleans.Balancer.csproj ├── SiloBuilderExtensions.cs ├── BalancerStartupTask.cs ├── BalancerGrain.cs └── LocalDeactivatorGrain.cs ├── .github └── workflows │ ├── nuget.yml │ ├── dotnet.yml │ └── codeql-analysis.yml ├── LICENSE ├── Orleans.Balancer.sln ├── Directory.Build.props ├── README.md └── .gitignore /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/managedcode/Orleans.Balancer/HEAD/logo.png -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/ITestGrain.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 2 | 3 | public interface ITestGrain : IGrainWithGuidKey, IDoGrain 4 | { 5 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/ITestGrainInt.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 2 | 3 | public interface ITestGrainInt : IGrainWithIntegerKey, IDoGrain 4 | { 5 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/ITestGrainString.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 2 | 3 | public interface ITestGrainString : IGrainWithStringKey, IDoGrain 4 | { 5 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/Attributes/DeactivationPriority.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Orleans.Balancer.Attributes; 2 | 3 | public enum DeactivationPriority 4 | { 5 | High = 0, 6 | Normal = 1, 7 | Low = 3 8 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/IDoGrain.cs: -------------------------------------------------------------------------------- 1 | using Orleans.Runtime; 2 | 3 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 4 | 5 | public interface IDoGrain 6 | { 7 | public Task Do(); 8 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/Abstractions/IBalancerGrain.cs: -------------------------------------------------------------------------------- 1 | using Orleans.Concurrency; 2 | 3 | namespace ManagedCode.Orleans.Balancer.Abstractions; 4 | 5 | public interface IBalancerGrain : IGrainWithIntegerKey 6 | { 7 | Task InitializeAsync(); 8 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/Abstractions/ILocalDeactivatorGrain.cs: -------------------------------------------------------------------------------- 1 | using Orleans.Concurrency; 2 | 3 | namespace ManagedCode.Orleans.Balancer.Abstractions; 4 | 5 | public interface ILocalDeactivatorGrain : IGrainWithStringKey 6 | { 7 | [OneWay] 8 | Task InitializeAsync(); 9 | 10 | [OneWay] 11 | Task DeactivateGrainsAsync(float percentage); 12 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/Attributes/CanBeDeactivatedAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ManagedCode.Orleans.Balancer.Attributes; 2 | 3 | [AttributeUsage(AttributeTargets.Class)] 4 | public class CanBeDeactivatedAttribute : Attribute 5 | { 6 | public CanBeDeactivatedAttribute() 7 | { 8 | } 9 | 10 | public CanBeDeactivatedAttribute(DeactivationPriority priority) 11 | { 12 | Priority = priority; 13 | } 14 | 15 | public DeactivationPriority Priority { get; set; } = DeactivationPriority.Normal; 16 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/BaseTests.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Tests.Cluster; 2 | using Orleans.TestingHost; 3 | 4 | namespace ManagedCode.Orleans.Balancer.Tests; 5 | 6 | public class BaseTests 7 | { 8 | protected static async Task DeployTestCluster(short initialSilosCount = 1) 9 | { 10 | var builder = new TestClusterBuilder(); 11 | builder.AddSiloBuilderConfigurator(); 12 | builder.Options.InitialSilosCount = initialSilosCount; 13 | var cluster = builder.Build(); 14 | await cluster.DeployAsync(); 15 | 16 | return cluster; 17 | } 18 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/TestSiloConfigurations.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Attributes; 2 | using ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 3 | using Orleans.TestingHost; 4 | 5 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster; 6 | 7 | public class TestSiloConfigurations : ISiloConfigurator 8 | { 9 | public void Configure(ISiloBuilder siloBuilder) 10 | { 11 | siloBuilder.ConfigureServices(services => 12 | { 13 | // services.AddSingleton(...); 14 | }); 15 | siloBuilder.UseOrleansBalancer(o => 16 | { 17 | o.GrainsForDeactivation.Add(typeof(TestGrainInt), DeactivationPriority.High); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/OrleansBalancerOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using ManagedCode.Orleans.Balancer.Attributes; 3 | 4 | namespace ManagedCode.Orleans.Balancer; 5 | 6 | public record OrleansBalancerOptions 7 | { 8 | public Dictionary GrainsForDeactivation = new(); 9 | public int TotalGrainActivationsMinimumThreshold { get; set; } = 5000; 10 | 11 | public int DeactivateGrainsAtTheSameTime { get; set; } = 100; 12 | 13 | public TimeSpan DelayBetweenDeactivations { get; set; } = TimeSpan.FromSeconds(1); 14 | 15 | public TimeSpan TimerIntervalDeactivation { get; set; } = TimeSpan.FromSeconds(10); 16 | 17 | public TimeSpan TimerIntervalRebalancing { get; set; } = TimeSpan.FromSeconds(10); 18 | 19 | [Range(0f, 1f)] 20 | public float ReBalancingPercentage { get; set; } = 0.33f; 21 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/TestGrainInt.cs: -------------------------------------------------------------------------------- 1 | using Orleans.Runtime; 2 | 3 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 4 | 5 | //[CanBeDeactivated] from config, priory High 6 | public class TestGrainInt : Grain, ITestGrainInt 7 | { 8 | public static int ActivationCount; 9 | public static int DeactivationCount; 10 | 11 | public Task Do() 12 | { 13 | return Task.FromResult(this.GetGrainId()); 14 | } 15 | 16 | public override Task OnActivateAsync(CancellationToken cancellationToken) 17 | { 18 | Interlocked.Increment(ref ActivationCount); 19 | return base.OnActivateAsync(cancellationToken); 20 | } 21 | 22 | public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) 23 | { 24 | Interlocked.Increment(ref DeactivationCount); 25 | return base.OnDeactivateAsync(reason, cancellationToken); 26 | } 27 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/TestGrain.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Attributes; 2 | using Orleans.Runtime; 3 | 4 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 5 | 6 | [CanBeDeactivated] 7 | public class TestGrain : Grain, ITestGrain 8 | { 9 | public static int ActivationCount; 10 | public static int DeactivationCount; 11 | 12 | public Task Do() 13 | { 14 | return Task.FromResult(this.GetGrainId()); 15 | } 16 | 17 | public override Task OnActivateAsync(CancellationToken cancellationToken) 18 | { 19 | Interlocked.Increment(ref ActivationCount); 20 | return base.OnActivateAsync(cancellationToken); 21 | } 22 | 23 | public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) 24 | { 25 | Interlocked.Increment(ref DeactivationCount); 26 | return base.OnDeactivateAsync(reason, cancellationToken); 27 | } 28 | } -------------------------------------------------------------------------------- /.github/workflows/nuget.yml: -------------------------------------------------------------------------------- 1 | name: nuget 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | nuget-pack: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v3 19 | with: 20 | dotnet-version: 7.0.x 21 | 22 | - name: Restore dependencies 23 | run: dotnet restore 24 | - name: Build 25 | run: dotnet build --configuration Release 26 | - name: Pack 27 | run: dotnet pack -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg --configuration Release 28 | 29 | - name: publish nuget packages 30 | run: | 31 | shopt -s globstar 32 | for file in **/*.nupkg 33 | do 34 | dotnet nuget push "$file" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 35 | done -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/Cluster/Grains/TestGrainString.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Attributes; 2 | using Orleans.Runtime; 3 | 4 | namespace ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 5 | 6 | [CanBeDeactivated(DeactivationPriority.Low)] 7 | public class TestGrainString : Grain, ITestGrainString 8 | { 9 | public static int ActivationCount; 10 | public static int DeactivationCount; 11 | 12 | public Task Do() 13 | { 14 | return Task.FromResult(this.GetGrainId()); 15 | } 16 | 17 | public override Task OnActivateAsync(CancellationToken cancellationToken) 18 | { 19 | Interlocked.Increment(ref ActivationCount); 20 | return base.OnActivateAsync(cancellationToken); 21 | } 22 | 23 | public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) 24 | { 25 | Interlocked.Increment(ref DeactivationCount); 26 | return base.OnDeactivateAsync(reason, cancellationToken); 27 | } 28 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/ManagedCode.Orleans.Balancer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 11 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ManagedCode.Orleans.Balancer 18 | ManagedCode.Orleans.Balancer 19 | Orleans grain balancer 20 | managedcode, orleans, blancer 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/SiloBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace ManagedCode.Orleans.Balancer; 4 | 5 | public static class SiloBuilderExtensions 6 | { 7 | public static ISiloBuilder UseOrleansBalancer(this ISiloBuilder siloBuilder) 8 | { 9 | return UseOrleansBalancer(siloBuilder, _ => { }); 10 | } 11 | 12 | public static ISiloBuilder UseOrleansBalancer(this ISiloBuilder siloBuilder, Action options) 13 | { 14 | siloBuilder.AddStartupTask(); 15 | 16 | siloBuilder.ConfigureServices(serviceCollection => 17 | { 18 | serviceCollection.AddOptions() 19 | //.Bind(context.Configuration.GetSection("ActivationShedding")) 20 | // ReSharper disable once ConvertClosureToMethodGroup 21 | .PostConfigure(sheddingOptions => options(sheddingOptions)); 22 | // .ValidateDataAnnotations(); 23 | }); 24 | 25 | return siloBuilder; 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Managed-Code 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. 22 | -------------------------------------------------------------------------------- /Orleans.Balancer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.Orleans.Balancer", "ManagedCode.Orleans.Balancer\ManagedCode.Orleans.Balancer.csproj", "{40843BE6-8851-4CAE-A9DE-8B1380252DEB}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.Orleans.Balancer.Tests", "ManagedCode.Orleans.Balancer.Tests\ManagedCode.Orleans.Balancer.Tests.csproj", "{6F852AFA-7D30-4820-9E1B-DEBB25864530}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {40843BE6-8851-4CAE-A9DE-8B1380252DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {40843BE6-8851-4CAE-A9DE-8B1380252DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {40843BE6-8851-4CAE-A9DE-8B1380252DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {40843BE6-8851-4CAE-A9DE-8B1380252DEB}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {6F852AFA-7D30-4820-9E1B-DEBB25864530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {6F852AFA-7D30-4820-9E1B-DEBB25864530}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {6F852AFA-7D30-4820-9E1B-DEBB25864530}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {6F852AFA-7D30-4820-9E1B-DEBB25864530}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | ManagedCode 7 | Copyright © 2021-$([System.DateTime]::Now.ToString(`yyyy`)) ManagedCode SAS 8 | https://github.com/managed-code-hub/Orleans.Balancer 9 | https://github.com/managed-code-hub/Orleans.Balancer 10 | Github 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | logo.png 13 | MIT 14 | README.md 15 | Managed Code - Orleans.Balancer 16 | 7.1.1 17 | 7.1.1 18 | 19 | 20 | true 21 | 22 | 23 | true 24 | snupkg 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | 15 | build-and-test: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | 20 | - uses: actions/checkout@v3 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v3 23 | with: 24 | dotnet-version: 7.0.x 25 | 26 | # run build and test 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | - name: Build 30 | run: dotnet build --no-restore 31 | - name: Test 32 | run: dotnet test --no-build --logger 'trx;LogFileName=test-results.trx' 33 | 34 | - name: Collect Code Coverage 35 | run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=ManagedCode.Keda.Tests/lcov.info 36 | 37 | # - name: test-reports 38 | # uses: dorny/test-reporter@v1.5.0 39 | # with: 40 | # name: Test Reporter 41 | # reporter: dotnet-trx 42 | # path: ManagedCode.Keda.Tests/test-results.trx 43 | 44 | - name : coverlet 45 | uses: b3b00/coverlet-action@1.1.9 46 | with: 47 | testProject: 'ManagedCode.Orleans.Balancer.Tests/ManagedCode.Orleans.Balancer.Tests.csproj' 48 | output: 'lcov.info' 49 | outputFormat: 'lcov' 50 | excludes: '[program]*,[test]test.*' 51 | - name: coveralls 52 | uses: coverallsapp/github-action@master 53 | with: 54 | github-token: ${{secrets.GITHUB_TOKEN }} 55 | path-to-lcov: ManagedCode.Orleans.Balancer.Tests/lcov.info -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/ManagedCode.Orleans.Balancer.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | all 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/BalancerStartupTask.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Abstractions; 2 | using Microsoft.Extensions.Logging; 3 | using Orleans.Runtime; 4 | 5 | namespace ManagedCode.Orleans.Balancer; 6 | 7 | public class BalancerStartupTask : IStartupTask 8 | { 9 | private readonly IGrainFactory _grainFactory; 10 | private readonly ILocalSiloDetails _localSiloDetails; 11 | private readonly ILogger _logger; 12 | 13 | public BalancerStartupTask(ILogger logger, IGrainFactory grainFactory, ILocalSiloDetails localSiloDetails) 14 | { 15 | _logger = logger; 16 | _grainFactory = grainFactory; 17 | _localSiloDetails = localSiloDetails; 18 | } 19 | 20 | public async Task Execute(CancellationToken cancellationToken) 21 | { 22 | await InitializeGrainsAsync(); 23 | _ = Task.Run(ExecuteInternal); 24 | } 25 | 26 | private async Task ExecuteInternal() 27 | { 28 | while (true) 29 | { 30 | await Task.Delay(TimeSpan.FromMinutes(2)); 31 | await InitializeGrainsAsync(); 32 | } 33 | } 34 | 35 | private async Task InitializeGrainsAsync() 36 | { 37 | try 38 | { 39 | await _grainFactory.GetGrain(0).InitializeAsync(); 40 | } 41 | catch (Exception e) 42 | { 43 | _logger.LogError(e, "IBalancerGrain throw an error."); 44 | } 45 | 46 | try 47 | { 48 | await _grainFactory.GetGrain(_localSiloDetails.SiloAddress.ToParsableString()).InitializeAsync(); 49 | } 50 | catch (Exception e) 51 | { 52 | _logger.LogError(e, "ILocalDeactivatorGrain throw an error."); 53 | } 54 | 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/LocalDeactivatorTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Orleans.Runtime; 3 | using Xunit; 4 | 5 | namespace ManagedCode.Orleans.Balancer.Tests; 6 | 7 | public class LocalDeactivatorTests : BaseTests 8 | { 9 | [Fact] 10 | public async Task ActivateSomeSilo_ShouldBeSameCountLocalDeactivators() 11 | { 12 | // Arrange 13 | short siloCount = 5; 14 | 15 | // Act 16 | var cluster = await DeployTestCluster(siloCount); 17 | await Task.Delay(1000); 18 | var managementGrain = cluster.GrainFactory.GetGrain(0); 19 | var detailedGrainStatistics = await managementGrain.GetDetailedGrainStatistics(); 20 | 21 | // Assert 22 | var localDeactivatorGrains = detailedGrainStatistics 23 | .Where(s => s.GrainType.Contains(nameof(LocalDeactivatorGrain))) 24 | .ToList(); 25 | 26 | localDeactivatorGrains.Count.Should().Be(siloCount); 27 | 28 | foreach (var localDeactivatorGrain in localDeactivatorGrains) 29 | { 30 | var grainKey = localDeactivatorGrain.GrainId.Key.ToString(); 31 | var siloAddress = localDeactivatorGrain.SiloAddress.ToParsableString(); 32 | 33 | grainKey.Should().Be(siloAddress); 34 | } 35 | } 36 | 37 | [Fact] 38 | public async Task ActivateLocalDeactivators_ShouldNotBeDeactivated() 39 | { 40 | // Arrange 41 | short siloCount = 5; 42 | 43 | // Act 44 | var cluster = await DeployTestCluster(siloCount); 45 | await Task.Delay(1000); 46 | var managementGrain = cluster.GrainFactory.GetGrain(0); 47 | await managementGrain.ForceActivationCollection(TimeSpan.FromMilliseconds(1)); 48 | 49 | //await Task.Delay(TimeSpan.FromMinutes(2.5)); 50 | 51 | var detailedGrainStatistics = await managementGrain.GetDetailedGrainStatistics(); 52 | 53 | // Assert 54 | var localDeactivatorGrains = detailedGrainStatistics 55 | .Where(s => s.GrainType.Contains(nameof(LocalDeactivatorGrain))) 56 | .ToList(); 57 | 58 | localDeactivatorGrains.Count.Should().Be(siloCount); 59 | } 60 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/BalancerGrain.cs: -------------------------------------------------------------------------------- 1 | using ManagedCode.Orleans.Balancer.Abstractions; 2 | using Microsoft.Extensions.Options; 3 | using Orleans.Concurrency; 4 | using Orleans.Runtime; 5 | 6 | namespace ManagedCode.Orleans.Balancer; 7 | 8 | [KeepAlive] 9 | [Reentrant] 10 | public class BalancerGrain : Grain, IBalancerGrain 11 | { 12 | private readonly OrleansBalancerOptions _options; 13 | private SiloAddress[] _silos = Array.Empty(); 14 | private bool _initialized; 15 | 16 | public BalancerGrain(IOptions options) 17 | { 18 | _options = options.Value; 19 | } 20 | 21 | public Task InitializeAsync() 22 | { 23 | // First call will return true, others will return false; 24 | 25 | if (_initialized) 26 | return Task.FromResult(false); 27 | 28 | _initialized = true; 29 | return Task.FromResult(_initialized); 30 | } 31 | 32 | public override async Task OnActivateAsync(CancellationToken cancellationToken) 33 | { 34 | var managementGrain = GrainFactory.GetGrain(0); 35 | var hosts = await managementGrain.GetHosts(true); 36 | _silos = hosts.Select(s => s.Key).ToArray(); 37 | 38 | RegisterTimer(CheckAsync, null, _options.TimerIntervalRebalancing, _options.TimerIntervalRebalancing); 39 | } 40 | 41 | public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken cancellationToken) 42 | { 43 | return base.OnDeactivateAsync(reason, cancellationToken); 44 | } 45 | 46 | private async Task CheckAsync(object obj) 47 | { 48 | var managementGrain = GrainFactory.GetGrain(0); 49 | var hosts = await managementGrain.GetHosts(true); 50 | 51 | if (hosts.Count > _silos.Length) 52 | { 53 | _ = ReBalanceAsync(_silos); 54 | } 55 | 56 | _silos = hosts.Select(s => s.Key).ToArray(); 57 | } 58 | 59 | public async Task ReBalanceAsync(IEnumerable silos) 60 | { 61 | var tasks = silos 62 | .Select(address => GrainFactory.GetGrain(address.ToParsableString())) 63 | .Select(grain => grain.DeactivateGrainsAsync(_options.ReBalancingPercentage)) 64 | .ToList(); 65 | 66 | await Task.WhenAll(tasks); 67 | } 68 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '35 11 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'csharp' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![img|300x200](https://raw.githubusercontent.com/managedcode/Orleans.Balancer/main/logo.png) 2 | 3 | # Orleans.Balancer 4 | 5 | [![.NET](https://github.com/managedcode/Orleans.Balancer/actions/workflows/dotnet.yml/badge.svg)](https://github.com/managedcode/Orleans.Balancer/actions/workflows/dotnet.yml) 6 | [![Coverage Status](https://coveralls.io/repos/github/managedcode/Orleans.Balancer/badge.svg?branch=main)](https://coveralls.io/github/managedcode/Orleans.Balancer?branch=main) 7 | [![nuget](https://github.com/managedcode/Orleans.Balancer/actions/workflows/nuget.yml/badge.svg?branch=main)](https://github.com/managedcode/Orleans.Balancer/actions/workflows/nuget.yml) 8 | [![CodeQL](https://github.com/managedcode/Orleans.Balancer/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/managedcode/Orleans.Balancer/actions/workflows/codeql-analysis.yml) 9 | 10 | Orleans.Balancer is a library for automatically balancing the number of active Grains in an Orleans distributed system. 11 | It allows you to set limits on the number of active Grains, and will automatically deactivate Grains if those limits are 12 | reached. It can also perform rebalancing of Grain activations between silos to ensure evenly distributed. 13 | 14 | ## Motivation 15 | 16 | Orleans is a distributed systems platform that makes it easy to build highly scalable, low-latency applications. 17 | However, managing the number of active Grains in an Orleans system can be challenging, especially in environments with 18 | unpredictable workloads. Orleans.Balancer solves this problem by providing automatic control over the number of active 19 | Grains, as well as tools for rebalancing activations between silos. This ensures that your Orleans applications have the 20 | resources they need to handle increased workloads without manual intervention. Use it together 21 | with https://github.com/managedcode/Keda 22 | 23 | ## Getting Started 24 | 25 | To use Orleans.Balancer, you will need to have an Orleans distributed system set up. Once you have that, you can install 26 | Orleans.Balancer using the provided NuGet package. 27 | 28 | ## Usage 29 | 30 | Orleans.Balancer is used by creating an instance of the Orleans.Balancer.Balancer class and passing in the desired 31 | settings. The Balancer class provides methods for controlling the number of active Grains, as well as for performing 32 | rebalancing operations. 33 | 34 | Install package ``` ManagedCode.Orleans.Balancer``` 35 | 36 | ```cs 37 | siloBuilder.UseOrleansBalancer(o => { }); 38 | ``` 39 | 40 | ## Contributing 41 | 42 | We welcome contributions to Orleans.Balancer! If you have an idea for a new feature or have found a bug, please open an 43 | issue on GitHub. 44 | 45 | ## Base on 46 | 47 | https://github.com/oising/OrleansContrib.ActivationShedding 48 | -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/BalancerGrainTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using ManagedCode.Orleans.Balancer.Abstractions; 3 | using Orleans.Runtime; 4 | using Xunit; 5 | 6 | namespace ManagedCode.Orleans.Balancer.Tests; 7 | 8 | public class BalancerGrainTests : BaseTests 9 | { 10 | [Fact] 11 | public async Task ActivateSomeSilo_ShouldBeOneBalancerGrain() 12 | { 13 | // Arrange 14 | short siloCount = 5; 15 | 16 | // Act 17 | var cluster = await DeployTestCluster(siloCount); 18 | await Task.Delay(2000); 19 | var managementGrain = cluster.GrainFactory.GetGrain(0); 20 | 21 | // Assert 22 | var balancerGrains = await GetBalancerGrainStatistics(managementGrain); 23 | balancerGrains.Count.Should().Be(1); 24 | } 25 | 26 | [Fact] 27 | public async Task ActivateBalancerGrain_ShouldNotBeDeactivated() 28 | { 29 | // Act 30 | var cluster = await DeployTestCluster(); 31 | 32 | await Task.Delay(1000); 33 | var managementGrain = cluster.GrainFactory.GetGrain(0); 34 | await managementGrain.ForceActivationCollection(TimeSpan.FromMilliseconds(1)); 35 | 36 | // Assert 37 | var balancerGrains = await GetBalancerGrainStatistics(managementGrain); 38 | balancerGrains.Count.Should().Be(1); 39 | } 40 | 41 | [Fact] 42 | public async Task RestartSiloWithBalancerGrains_BalancerGrainsShouldBeActive() 43 | { 44 | // Act 45 | var cluster = await DeployTestCluster(5); 46 | 47 | await Task.Delay(TimeSpan.FromSeconds(1)); 48 | var managementGrain = cluster.GrainFactory.GetGrain(0); 49 | await managementGrain.ForceActivationCollection(TimeSpan.FromMilliseconds(1)); 50 | 51 | var balancerGrain = (await GetBalancerGrainStatistics(managementGrain)).First(); 52 | 53 | await cluster.RestartSiloAsync(cluster.GetSiloForAddress(balancerGrain.SiloAddress)); 54 | await Task.Delay(TimeSpan.FromSeconds(10)); 55 | 56 | // Assert 57 | var balancerGrains = await GetBalancerGrainStatistics(managementGrain); 58 | if (balancerGrains.Count == 0) 59 | { 60 | // wait 5 min when startup task activated grains again 61 | await Task.Delay(TimeSpan.FromMinutes(2.5)); 62 | } 63 | 64 | balancerGrains = await GetBalancerGrainStatistics(managementGrain); 65 | balancerGrains.Count.Should().Be(1); 66 | } 67 | 68 | private static async Task> GetBalancerGrainStatistics(IManagementGrain managementGrain) 69 | { 70 | var detailedGrainStatistics = await managementGrain.GetDetailedGrainStatistics(); 71 | 72 | // Assert 73 | var balancerGrains = detailedGrainStatistics 74 | .Where(s => s.GrainType.Contains(nameof(BalancerGrain))) 75 | .ToList(); 76 | 77 | return balancerGrains; 78 | } 79 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer/LocalDeactivatorGrain.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using ManagedCode.Orleans.Balancer.Abstractions; 3 | using ManagedCode.Orleans.Balancer.Attributes; 4 | using Microsoft.Extensions.Options; 5 | using Orleans.Concurrency; 6 | using Orleans.Core.Internal; 7 | using Orleans.Placement; 8 | using Orleans.Runtime; 9 | 10 | namespace ManagedCode.Orleans.Balancer; 11 | 12 | [Reentrant] 13 | [KeepAlive] 14 | [PreferLocalPlacement] 15 | public class LocalDeactivatorGrain : Grain, ILocalDeactivatorGrain 16 | { 17 | private readonly Dictionary _grainTypes; 18 | private readonly SiloAddress[] _localSiloAddresses; 19 | private readonly OrleansBalancerOptions _options; 20 | 21 | public LocalDeactivatorGrain( 22 | ILocalSiloDetails siloDetails, 23 | IOptions options) 24 | { 25 | _options = options.Value; 26 | _localSiloAddresses = new[] { siloDetails.SiloAddress }; 27 | 28 | _grainTypes = AppDomain.CurrentDomain.GetAssemblies() 29 | .SelectMany(s => s.GetTypes()) 30 | .Where(CanDeactivate) 31 | .Select(s => (GetGrainType(s), GetPriority(s))) 32 | .Distinct() 33 | .ToDictionary(s => s.Item1, s => s.Item2); 34 | 35 | foreach (var (key, value) in _options.GrainsForDeactivation) 36 | { 37 | _grainTypes.TryAdd(GetGrainType(key), value); 38 | } 39 | } 40 | 41 | public Task InitializeAsync() 42 | { 43 | // Just for activate grain 44 | return Task.CompletedTask; 45 | } 46 | 47 | public async Task DeactivateGrainsAsync(float percentage) 48 | { 49 | if (percentage is <= 0 or > 1) 50 | { 51 | throw new ArgumentOutOfRangeException(nameof(percentage)); 52 | } 53 | 54 | var localActivations = await GetLocalActivationCount(); 55 | var countToDeactivate = (int)Math.Ceiling(localActivations * percentage); 56 | 57 | await DeactivateGrainsAsync(countToDeactivate); 58 | } 59 | 60 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 61 | private string GetGrainType(Type type) 62 | { 63 | return type.FullName + "," + type.Assembly.GetName().Name; 64 | } 65 | 66 | public override Task OnActivateAsync(CancellationToken cancellationToken) 67 | { 68 | RegisterTimer(CheckAsync, null, _options.TimerIntervalDeactivation, _options.TimerIntervalDeactivation); 69 | 70 | return base.OnActivateAsync(cancellationToken); 71 | } 72 | 73 | private async Task CheckAsync(object obj) 74 | { 75 | var localActivations = await GetLocalActivationCount(); 76 | 77 | if (localActivations >= _options.TotalGrainActivationsMinimumThreshold) 78 | { 79 | var countToDeactivate = localActivations - _options.TotalGrainActivationsMinimumThreshold; 80 | await DeactivateGrainsAsync(countToDeactivate); 81 | } 82 | } 83 | 84 | private async Task DeactivateGrainsAsync(int countToDeactivate) 85 | { 86 | if (countToDeactivate < 1) 87 | { 88 | return; 89 | } 90 | 91 | var managementGrain = GrainFactory.GetGrain(0); 92 | 93 | var grainStatistics = 94 | await managementGrain.GetDetailedGrainStatistics(_grainTypes.Select(s => s.Key).ToArray(), _localSiloAddresses); 95 | 96 | Queue> deactivationTasks = new(); 97 | 98 | foreach (var grainStatistic in grainStatistics.OrderBy(s => _grainTypes[s.GrainType])) 99 | { 100 | if (countToDeactivate <= 0) 101 | { 102 | break; 103 | } 104 | 105 | var addressable = GrainFactory.GetGrain(grainStatistic.GrainId); 106 | deactivationTasks.Enqueue(() => addressable.Cast().DeactivateOnIdle()); 107 | 108 | countToDeactivate--; 109 | } 110 | 111 | var chunks = deactivationTasks 112 | .Chunk(_options.DeactivateGrainsAtTheSameTime); 113 | //.Chunk(ThreadPool.ThreadCount / 4); //TODO: Experiment with TheadPool capacity; 114 | 115 | foreach (var chunk in chunks) 116 | { 117 | await Task.WhenAll(chunk.Select(task => task())); 118 | await Task.Delay(_options.DelayBetweenDeactivations); 119 | } 120 | } 121 | 122 | private async Task GetLocalActivationCount() 123 | { 124 | var managementGrain = GrainFactory.GetGrain(0); 125 | 126 | var localStatistics = await managementGrain.GetRuntimeStatistics(_localSiloAddresses); 127 | return localStatistics.Sum(s => s.ActivationCount); 128 | } 129 | 130 | private static bool CanDeactivate(Type type) 131 | { 132 | return Attribute.GetCustomAttribute(type, typeof(CanBeDeactivatedAttribute)) is not null; 133 | } 134 | 135 | private static DeactivationPriority GetPriority(Type type) 136 | { 137 | var attribute = Attribute.GetCustomAttribute(type, typeof(CanBeDeactivatedAttribute)) as CanBeDeactivatedAttribute; 138 | if (attribute is null) 139 | { 140 | return DeactivationPriority.Normal; 141 | } 142 | 143 | return attribute!.Priority; 144 | } 145 | } -------------------------------------------------------------------------------- /ManagedCode.Orleans.Balancer.Tests/SiloTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using ManagedCode.Orleans.Balancer.Tests.Cluster; 3 | using ManagedCode.Orleans.Balancer.Tests.Cluster.Grains; 4 | using Orleans.Runtime; 5 | using Orleans.TestingHost; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace ManagedCode.Orleans.Balancer.Tests; 10 | 11 | public class SiloTests 12 | { 13 | private readonly ITestOutputHelper _outputHelper; 14 | private volatile int _errors; 15 | 16 | public SiloTests(ITestOutputHelper outputHelper) 17 | { 18 | _outputHelper = outputHelper; 19 | } 20 | 21 | private async Task GetStatistics(TestCluster cluster) 22 | { 23 | var mg = cluster.GrainFactory.GetGrain(0); 24 | var statistics = await mg.GetRuntimeStatistics(cluster.Silos.Select(s => s.SiloAddress).ToArray()); 25 | return statistics; 26 | } 27 | 28 | private async Task RunTestGrain(TestCluster cluster, int iterations) 29 | { 30 | for (var i = 0; i < iterations; i++) 31 | { 32 | var testGrain = cluster.Client.GetGrain(Guid.NewGuid()); 33 | var testGrainInt = cluster.Client.GetGrain(i); 34 | var testGrainString = cluster.Client.GetGrain(i.ToString()); 35 | 36 | await Task.WhenAll(testGrain.Do(), testGrainInt.Do(), testGrainString.Do()); 37 | } 38 | } 39 | 40 | private async Task SiloTest(int iteration, bool enableBalance) 41 | { 42 | var builder = new TestClusterBuilder(); 43 | if (enableBalance) 44 | { 45 | builder.AddSiloBuilderConfigurator(); 46 | } 47 | 48 | builder.Options.InitialSilosCount = 1; 49 | 50 | var cluster = builder.Build(); 51 | await cluster.DeployAsync(); 52 | 53 | var total = iteration / 10; 54 | for (var loop = 0; loop < total; loop++) 55 | { 56 | var sw = Stopwatch.StartNew(); 57 | Parallel.For(1, iteration * 10, i => { Task.WaitAll(RunTestGrain(cluster, iteration)); }); 58 | sw.Stop(); 59 | 60 | _outputHelper.WriteLine($"Interation:{loop}/{total} - {sw.Elapsed}"); 61 | await cluster.StartAdditionalSiloAsync(); 62 | } 63 | 64 | await Task.Delay(TimeSpan.FromSeconds(30)); 65 | 66 | var final = Stopwatch.StartNew(); 67 | Parallel.For(1, iteration * 10, i => { Task.WaitAll(RunTestGrain(cluster, iteration)); }); 68 | final.Stop(); 69 | 70 | _outputHelper.WriteLine($"Interation:Final - {final.Elapsed}"); 71 | 72 | var stat = await GetStatistics(cluster); 73 | var count = 0; 74 | 75 | foreach (var silo in stat) 76 | { 77 | count++; 78 | _outputHelper.WriteLine( 79 | $"Silo:{count}; ActivationCount:{silo.ActivationCount}; RecentlyUsedActivationCount:{silo.RecentlyUsedActivationCount}; SentMessages:{silo.SentMessages}"); 80 | _outputHelper.WriteLine($" SentMessages:{silo.SentMessages}; ReceivedMessages:{silo.ReceivedMessages};"); 81 | } 82 | 83 | _outputHelper.WriteLine($"Total ActivationCount:{TestGrain.ActivationCount}"); 84 | _outputHelper.WriteLine($"Total DeactivationCount:{TestGrain.DeactivationCount}"); 85 | _outputHelper.WriteLine($"Total ERRORS:{_errors}"); 86 | } 87 | 88 | [Theory] 89 | [InlineData(10)] 90 | [InlineData(25)] 91 | [InlineData(50)] 92 | //[InlineData(100)] 93 | public async Task ClearSiloTest(int itereations) 94 | { 95 | _errors = 0; 96 | await SiloTest(itereations, false); 97 | } 98 | 99 | [Theory] 100 | [InlineData(10)] 101 | [InlineData(25)] 102 | [InlineData(50)] 103 | //[InlineData(100)] 104 | public async Task BalancerSiloTest(int itereations) 105 | { 106 | _errors = 0; 107 | await SiloTest(itereations, true); 108 | } 109 | 110 | [Theory] 111 | [InlineData(250_000)] 112 | [InlineData(500_000)] 113 | public async Task ClearSiloSingleThreadTest(int itereations) 114 | { 115 | _errors = 0; 116 | await SingleThread(itereations, false); 117 | } 118 | 119 | [Theory] 120 | [InlineData(250_000)] 121 | [InlineData(500_000)] 122 | public async Task BalancerSiloSingleThreadTest(int itereations) 123 | { 124 | _errors = 0; 125 | await SingleThread(itereations, true); 126 | } 127 | 128 | private async Task SingleThread(int iteration, bool enableBalance) 129 | { 130 | _errors = 0; 131 | var builder = new TestClusterBuilder(); 132 | if (enableBalance) 133 | { 134 | builder.AddSiloBuilderConfigurator(); 135 | } 136 | 137 | builder.Options.InitialSilosCount = 1; 138 | 139 | var cluster = builder.Build(); 140 | await cluster.DeployAsync(); 141 | 142 | var rnd = new Random(); 143 | for (var i = 0; i < iteration; i++) 144 | { 145 | if (i % 100_000 == 0) 146 | { 147 | await cluster.StartAdditionalSiloAsync(); 148 | _outputHelper.WriteLine("Cluster is added - " + i); 149 | } 150 | 151 | var newRnd = rnd.Next(0, 2); 152 | if (newRnd == 0) 153 | { 154 | var hello = cluster.Client.GetGrain(rnd.Next(0, 30_000)); 155 | try 156 | { 157 | var id = await hello.Do(); 158 | } 159 | catch (Exception e) 160 | { 161 | Interlocked.Increment(ref _errors); 162 | if (Debugger.IsAttached) 163 | { 164 | _outputHelper.WriteLine($"!!!Error:{e.Message}"); 165 | } 166 | } 167 | } 168 | else if (newRnd == 1) 169 | { 170 | var hello = cluster.Client.GetGrain(Guid.NewGuid()); 171 | try 172 | { 173 | var id = await hello.Do(); 174 | } 175 | catch (Exception e) 176 | { 177 | Interlocked.Increment(ref _errors); 178 | if (Debugger.IsAttached) 179 | { 180 | _outputHelper.WriteLine($"!!!Error:{e.Message}"); 181 | } 182 | } 183 | } 184 | else 185 | { 186 | var hello = cluster.Client.GetGrain(rnd.Next(0, 30_000).ToString()); 187 | try 188 | { 189 | var id = await hello.Do(); 190 | } 191 | catch (Exception e) 192 | { 193 | Interlocked.Increment(ref _errors); 194 | if (Debugger.IsAttached) 195 | { 196 | _outputHelper.WriteLine($"!!!Error:{e.Message}"); 197 | } 198 | } 199 | } 200 | } 201 | 202 | var stat = await GetStatistics(cluster); 203 | var count = 0; 204 | 205 | foreach (var silo in stat) 206 | { 207 | count++; 208 | _outputHelper.WriteLine( 209 | $"Silo:{count}; ActivationCount:{silo.ActivationCount}; RecentlyUsedActivationCount:{silo.RecentlyUsedActivationCount}; SentMessages:{silo.SentMessages}"); 210 | _outputHelper.WriteLine($" SentMessages:{silo.SentMessages}; ReceivedMessages:{silo.ReceivedMessages};"); 211 | } 212 | 213 | _outputHelper.WriteLine($"Total ActivationCount:{TestGrainInt.ActivationCount}"); 214 | _outputHelper.WriteLine($"Total DeactivationCount:{TestGrainInt.DeactivationCount}"); 215 | _outputHelper.WriteLine($"Total ERRORS:{_errors}"); 216 | } 217 | 218 | [Fact] 219 | public async Task SchreddingGrains() 220 | { 221 | var builder = new TestClusterBuilder(); 222 | builder.AddSiloBuilderConfigurator(); 223 | builder.Options.InitialSilosCount = 3; 224 | 225 | var cluster = builder.Build(); 226 | await cluster.DeployAsync(); 227 | 228 | for (var i = 0; i < 10; i++) 229 | { 230 | var hello1 = await cluster.Client.GetGrain(i).Do(); 231 | var hello2 = await cluster.Client.GetGrain(Guid.NewGuid()).Do(); 232 | } 233 | 234 | var grain = cluster.Client.GetGrain(0); 235 | var hosts = await grain.GetHosts(); 236 | var xx1 = await grain.GetSimpleGrainStatistics(); 237 | var xx2 = await grain.GetRuntimeStatistics(new[] { hosts.First().Key }); 238 | var xx3 = await grain.GetDetailedGrainStatistics(hostsIds: new[] { hosts.First().Key }); 239 | 240 | await Task.Delay(TimeSpan.FromSeconds(10)); 241 | 242 | for (var i = 0; i < 10; i++) 243 | { 244 | var hello1 = await cluster.Client.GetGrain(i).Do(); 245 | var hello2 = await cluster.Client.GetGrain(Guid.NewGuid()).Do(); 246 | } 247 | 248 | await Task.Delay(TimeSpan.FromSeconds(10)); 249 | 250 | for (var i = 0; i < 10; i++) 251 | { 252 | var hello1 = await cluster.Client.GetGrain(i).Do(); 253 | var hello2 = await cluster.Client.GetGrain(Guid.NewGuid()).Do(); 254 | } 255 | 256 | await Task.Delay(TimeSpan.FromSeconds(30)); 257 | 258 | xx1 = await grain.GetSimpleGrainStatistics(); 259 | xx2 = await grain.GetRuntimeStatistics(new[] { hosts.First().Key }); 260 | xx3 = await grain.GetDetailedGrainStatistics(hostsIds: new[] { hosts.First().Key }); 261 | 262 | if (Debugger.IsAttached) 263 | { 264 | Debugger.Break(); 265 | } 266 | } 267 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/artifacts 37 | # .idea/compiler.xml 38 | # .idea/jarRepositories.xml 39 | # .idea/modules.xml 40 | # .idea/*.iml 41 | # .idea/modules 42 | # *.iml 43 | # *.ipr 44 | 45 | # CMake 46 | cmake-build-*/ 47 | 48 | # Mongo Explorer plugin 49 | .idea/**/mongoSettings.xml 50 | 51 | # File-based project format 52 | *.iws 53 | 54 | # IntelliJ 55 | out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Cursive Clojure plugin 64 | .idea/replstate.xml 65 | 66 | # Crashlytics plugin (for Android Studio and IntelliJ) 67 | com_crashlytics_export_strings.xml 68 | crashlytics.properties 69 | crashlytics-build.properties 70 | fabric.properties 71 | 72 | # Editor-based Rest Client 73 | .idea/httpRequests 74 | 75 | # Android studio 3.1+ serialized cache file 76 | .idea/caches/build_file_checksums.ser 77 | 78 | ### Intellij Patch ### 79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 80 | 81 | # *.iml 82 | # modules.xml 83 | # .idea/misc.xml 84 | # *.ipr 85 | 86 | # Sonarlint plugin 87 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 88 | .idea/**/sonarlint/ 89 | 90 | # SonarQube Plugin 91 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 92 | .idea/**/sonarIssues.xml 93 | 94 | # Markdown Navigator plugin 95 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 96 | .idea/**/markdown-navigator.xml 97 | .idea/**/markdown-navigator-enh.xml 98 | .idea/**/markdown-navigator/ 99 | 100 | # Cache file creation bug 101 | # See https://youtrack.jetbrains.com/issue/JBR-2257 102 | .idea/$CACHE_FILE$ 103 | 104 | # CodeStream plugin 105 | # https://plugins.jetbrains.com/plugin/12206-codestream 106 | .idea/codestream.xml 107 | 108 | ### Intellij+all ### 109 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 110 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 111 | 112 | # User-specific stuff 113 | 114 | # Generated files 115 | 116 | # Sensitive or high-churn files 117 | 118 | # Gradle 119 | 120 | # Gradle and Maven with auto-import 121 | # When using Gradle or Maven with auto-import, you should exclude module files, 122 | # since they will be recreated, and may cause churn. Uncomment if using 123 | # auto-import. 124 | # .idea/artifacts 125 | # .idea/compiler.xml 126 | # .idea/jarRepositories.xml 127 | # .idea/modules.xml 128 | # .idea/*.iml 129 | # .idea/modules 130 | # *.iml 131 | # *.ipr 132 | 133 | # CMake 134 | 135 | # Mongo Explorer plugin 136 | 137 | # File-based project format 138 | 139 | # IntelliJ 140 | 141 | # mpeltonen/sbt-idea plugin 142 | 143 | # JIRA plugin 144 | 145 | # Cursive Clojure plugin 146 | 147 | # Crashlytics plugin (for Android Studio and IntelliJ) 148 | 149 | # Editor-based Rest Client 150 | 151 | # Android studio 3.1+ serialized cache file 152 | 153 | ### Intellij+all Patch ### 154 | # Ignores the whole .idea folder and all .iml files 155 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 156 | 157 | .idea/ 158 | 159 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 160 | 161 | *.iml 162 | modules.xml 163 | .idea/misc.xml 164 | *.ipr 165 | 166 | # Sonarlint plugin 167 | .idea/sonarlint 168 | 169 | ### Linux ### 170 | *~ 171 | 172 | # temporary files which can be created if a process still has a handle open of a deleted file 173 | .fuse_hidden* 174 | 175 | # KDE directory preferences 176 | .directory 177 | 178 | # Linux trash folder which might appear on any partition or disk 179 | .Trash-* 180 | 181 | # .nfs files are created when an open file is removed but is still being accessed 182 | .nfs* 183 | 184 | ### macOS ### 185 | # General 186 | .DS_Store 187 | .AppleDouble 188 | .LSOverride 189 | 190 | # Icon must end with two \r 191 | Icon 192 | 193 | 194 | # Thumbnails 195 | ._* 196 | 197 | # Files that might appear in the root of a volume 198 | .DocumentRevisions-V100 199 | .fseventsd 200 | .Spotlight-V100 201 | .TemporaryItems 202 | .Trashes 203 | .VolumeIcon.icns 204 | .com.apple.timemachine.donotpresent 205 | 206 | # Directories potentially created on remote AFP share 207 | .AppleDB 208 | .AppleDesktop 209 | Network Trash Folder 210 | Temporary Items 211 | .apdisk 212 | 213 | ### Rider ### 214 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 215 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 216 | 217 | # User-specific stuff 218 | 219 | # Generated files 220 | 221 | # Sensitive or high-churn files 222 | 223 | # Gradle 224 | 225 | # Gradle and Maven with auto-import 226 | # When using Gradle or Maven with auto-import, you should exclude module files, 227 | # since they will be recreated, and may cause churn. Uncomment if using 228 | # auto-import. 229 | # .idea/artifacts 230 | # .idea/compiler.xml 231 | # .idea/jarRepositories.xml 232 | # .idea/modules.xml 233 | # .idea/*.iml 234 | # .idea/modules 235 | # *.iml 236 | # *.ipr 237 | 238 | # CMake 239 | 240 | # Mongo Explorer plugin 241 | 242 | # File-based project format 243 | 244 | # IntelliJ 245 | 246 | # mpeltonen/sbt-idea plugin 247 | 248 | # JIRA plugin 249 | 250 | # Cursive Clojure plugin 251 | 252 | # Crashlytics plugin (for Android Studio and IntelliJ) 253 | 254 | # Editor-based Rest Client 255 | 256 | # Android studio 3.1+ serialized cache file 257 | 258 | ### VisualStudioCode ### 259 | .vscode/* 260 | !.vscode/tasks.json 261 | !.vscode/launch.json 262 | *.code-workspace 263 | 264 | ### VisualStudioCode Patch ### 265 | # Ignore all local history of files 266 | .history 267 | .ionide 268 | 269 | ### Windows ### 270 | # Windows thumbnail cache files 271 | Thumbs.db 272 | Thumbs.db:encryptable 273 | ehthumbs.db 274 | ehthumbs_vista.db 275 | 276 | # Dump file 277 | *.stackdump 278 | 279 | # Folder config file 280 | [Dd]esktop.ini 281 | 282 | # Recycle Bin used on file shares 283 | $RECYCLE.BIN/ 284 | 285 | # Windows Installer files 286 | *.cab 287 | *.msi 288 | *.msix 289 | *.msm 290 | *.msp 291 | 292 | # Windows shortcuts 293 | *.lnk 294 | 295 | ### VisualStudio ### 296 | ## Ignore Visual Studio temporary files, build results, and 297 | ## files generated by popular Visual Studio add-ons. 298 | ## 299 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 300 | 301 | # User-specific files 302 | *.rsuser 303 | *.suo 304 | *.user 305 | *.userosscache 306 | *.sln.docstates 307 | 308 | # User-specific files (MonoDevelop/Xamarin Studio) 309 | *.userprefs 310 | 311 | # Mono auto generated files 312 | mono_crash.* 313 | 314 | # Build results 315 | [Dd]ebug/ 316 | [Dd]ebugPublic/ 317 | [Rr]elease/ 318 | [Rr]eleases/ 319 | x64/ 320 | x86/ 321 | [Aa][Rr][Mm]/ 322 | [Aa][Rr][Mm]64/ 323 | bld/ 324 | [Bb]in/ 325 | [Oo]bj/ 326 | [Ll]og/ 327 | [Ll]ogs/ 328 | 329 | # Visual Studio 2015/2017 cache/options directory 330 | .vs/ 331 | # Uncomment if you have tasks that create the project's static files in wwwroot 332 | #wwwroot/ 333 | 334 | # Visual Studio 2017 auto generated files 335 | Generated\ Files/ 336 | 337 | # MSTest test Results 338 | [Tt]est[Rr]esult*/ 339 | [Bb]uild[Ll]og.* 340 | 341 | # NUnit 342 | *.VisualState.xml 343 | TestResult.xml 344 | nunit-*.xml 345 | 346 | # Build Results of an ATL Project 347 | [Dd]ebugPS/ 348 | [Rr]eleasePS/ 349 | dlldata.c 350 | 351 | # Benchmark Results 352 | BenchmarkDotNet.Artifacts/ 353 | 354 | # .NET Core 355 | project.lock.json 356 | project.fragment.lock.json 357 | artifacts/ 358 | 359 | # StyleCop 360 | StyleCopReport.xml 361 | 362 | # Files built by Visual Studio 363 | *_i.c 364 | *_p.c 365 | *_h.h 366 | *.ilk 367 | *.meta 368 | *.obj 369 | *.iobj 370 | *.pch 371 | *.pdb 372 | *.ipdb 373 | *.pgc 374 | *.pgd 375 | *.rsp 376 | *.sbr 377 | *.tlb 378 | *.tli 379 | *.tlh 380 | *.tmp 381 | *.tmp_proj 382 | *_wpftmp.csproj 383 | *.log 384 | *.vspscc 385 | *.vssscc 386 | .builds 387 | *.pidb 388 | *.svclog 389 | *.scc 390 | 391 | # Chutzpah Test files 392 | _Chutzpah* 393 | 394 | # Visual C++ cache files 395 | ipch/ 396 | *.aps 397 | *.ncb 398 | *.opendb 399 | *.opensdf 400 | *.sdf 401 | *.cachefile 402 | *.VC.db 403 | *.VC.VC.opendb 404 | 405 | # Visual Studio profiler 406 | *.psess 407 | *.vsp 408 | *.vspx 409 | *.sap 410 | 411 | # Visual Studio Trace Files 412 | *.e2e 413 | 414 | # TFS 2012 Local Workspace 415 | $tf/ 416 | 417 | # Guidance Automation Toolkit 418 | *.gpState 419 | 420 | # ReSharper is a .NET coding add-in 421 | _ReSharper*/ 422 | *.[Rr]e[Ss]harper 423 | *.DotSettings.user 424 | 425 | # TeamCity is a build add-in 426 | _TeamCity* 427 | 428 | # DotCover is a Code Coverage Tool 429 | *.dotCover 430 | 431 | # AxoCover is a Code Coverage Tool 432 | .axoCover/* 433 | !.axoCover/settings.json 434 | 435 | # Coverlet is a free, cross platform Code Coverage Tool 436 | coverage*[.json, .xml, .info] 437 | 438 | # Visual Studio code coverage results 439 | *.coverage 440 | *.coveragexml 441 | 442 | # NCrunch 443 | _NCrunch_* 444 | .*crunch*.local.xml 445 | nCrunchTemp_* 446 | 447 | # MightyMoose 448 | *.mm.* 449 | AutoTest.Net/ 450 | 451 | # Web workbench (sass) 452 | .sass-cache/ 453 | 454 | # Installshield output folder 455 | [Ee]xpress/ 456 | 457 | # DocProject is a documentation generator add-in 458 | DocProject/buildhelp/ 459 | DocProject/Help/*.HxT 460 | DocProject/Help/*.HxC 461 | DocProject/Help/*.hhc 462 | DocProject/Help/*.hhk 463 | DocProject/Help/*.hhp 464 | DocProject/Help/Html2 465 | DocProject/Help/html 466 | 467 | # Click-Once directory 468 | publish/ 469 | 470 | # Publish Web Output 471 | *.[Pp]ublish.xml 472 | *.azurePubxml 473 | # Note: Comment the next line if you want to checkin your web deploy settings, 474 | # but database connection strings (with potential passwords) will be unencrypted 475 | *.pubxml 476 | *.publishproj 477 | 478 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 479 | # checkin your Azure Web App publish settings, but sensitive information contained 480 | # in these scripts will be unencrypted 481 | PublishScripts/ 482 | 483 | # NuGet Packages 484 | *.nupkg 485 | # NuGet Symbol Packages 486 | *.snupkg 487 | # The packages folder can be ignored because of Package Restore 488 | **/[Pp]ackages/* 489 | # except build/, which is used as an MSBuild target. 490 | !**/[Pp]ackages/build/ 491 | # Uncomment if necessary however generally it will be regenerated when needed 492 | #!**/[Pp]ackages/repositories.config 493 | # NuGet v3's project.json files produces more ignorable files 494 | *.nuget.props 495 | *.nuget.targets 496 | 497 | # Microsoft Azure Build Output 498 | csx/ 499 | *.build.csdef 500 | 501 | # Microsoft Azure Emulator 502 | ecf/ 503 | rcf/ 504 | 505 | # Windows Store app package directories and files 506 | AppPackages/ 507 | BundleArtifacts/ 508 | Package.StoreAssociation.xml 509 | _pkginfo.txt 510 | *.appx 511 | *.appxbundle 512 | *.appxupload 513 | 514 | # Visual Studio cache files 515 | # files ending in .cache can be ignored 516 | *.[Cc]ache 517 | # but keep track of directories ending in .cache 518 | !?*.[Cc]ache/ 519 | 520 | # Others 521 | ClientBin/ 522 | ~$* 523 | *.dbmdl 524 | *.dbproj.schemaview 525 | *.jfm 526 | *.pfx 527 | *.publishsettings 528 | orleans.codegen.cs 529 | 530 | # Including strong name files can present a security risk 531 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 532 | #*.snk 533 | 534 | # Since there are multiple workflows, uncomment next line to ignore bower_components 535 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 536 | #bower_components/ 537 | 538 | # RIA/Silverlight projects 539 | Generated_Code/ 540 | 541 | # Backup & report files from converting an old project file 542 | # to a newer Visual Studio version. Backup files are not needed, 543 | # because we have git ;-) 544 | _UpgradeReport_Files/ 545 | Backup*/ 546 | UpgradeLog*.XML 547 | UpgradeLog*.htm 548 | ServiceFabricBackup/ 549 | *.rptproj.bak 550 | 551 | # SQL Server files 552 | *.mdf 553 | *.ldf 554 | *.ndf 555 | 556 | # Business Intelligence projects 557 | *.rdl.data 558 | *.bim.layout 559 | *.bim_*.settings 560 | *.rptproj.rsuser 561 | *- [Bb]ackup.rdl 562 | *- [Bb]ackup ([0-9]).rdl 563 | *- [Bb]ackup ([0-9][0-9]).rdl 564 | 565 | # Microsoft Fakes 566 | FakesAssemblies/ 567 | 568 | # GhostDoc plugin setting file 569 | *.GhostDoc.xml 570 | 571 | # Node.js Tools for Visual Studio 572 | .ntvs_analysis.dat 573 | node_modules/ 574 | 575 | # Visual Studio 6 build log 576 | *.plg 577 | 578 | # Visual Studio 6 workspace options file 579 | *.opt 580 | 581 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 582 | *.vbw 583 | 584 | # Visual Studio LightSwitch build output 585 | **/*.HTMLClient/GeneratedArtifacts 586 | **/*.DesktopClient/GeneratedArtifacts 587 | **/*.DesktopClient/ModelManifest.xml 588 | **/*.Server/GeneratedArtifacts 589 | **/*.Server/ModelManifest.xml 590 | _Pvt_Extensions 591 | 592 | # Paket dependency manager 593 | .paket/paket.exe 594 | paket-files/ 595 | 596 | # FAKE - F# Make 597 | .fake/ 598 | 599 | # CodeRush personal settings 600 | .cr/personal 601 | 602 | # Python Tools for Visual Studio (PTVS) 603 | __pycache__/ 604 | *.pyc 605 | 606 | # Cake - Uncomment if you are using it 607 | # tools/** 608 | # !tools/packages.config 609 | 610 | # Tabs Studio 611 | *.tss 612 | 613 | # Telerik's JustMock configuration file 614 | *.jmconfig 615 | 616 | # BizTalk build output 617 | *.btp.cs 618 | *.btm.cs 619 | *.odx.cs 620 | *.xsd.cs 621 | 622 | # OpenCover UI analysis results 623 | OpenCover/ 624 | 625 | # Azure Stream Analytics local run output 626 | ASALocalRun/ 627 | 628 | # MSBuild Binary and Structured Log 629 | *.binlog 630 | 631 | # NVidia Nsight GPU debugger configuration file 632 | *.nvuser 633 | 634 | # MFractors (Xamarin productivity tool) working folder 635 | .mfractor/ 636 | 637 | # Local History for Visual Studio 638 | .localhistory/ 639 | 640 | # BeatPulse healthcheck temp database 641 | healthchecksdb 642 | 643 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 644 | MigrationBackup/ 645 | 646 | # Ionide (cross platform F# VS Code tools) working folder 647 | .ionide/ 648 | 649 | # End of https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider --------------------------------------------------------------------------------