From c321edf06dde04f61f4584249742dd9cd929f82d Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 11 Feb 2022 16:40:20 +0100 Subject: [PATCH 1/9] InteractionProfilerGrain --- OrleansDashboard.Core/IDashboardGrain.cs | 5 + .../Metrics/IGrainInteractionProfiler.cs | 15 +++ .../Model/DashboardCounters.cs | 1 + .../Model/GrainInteractionInfoEntry.cs | 15 +++ .../Implementation/GrainInteractionFilter.cs | 101 +++++++++++++++++ .../Implementation/Grains/DashboardGrain.cs | 15 ++- .../Grains/InteractionProfilerGrain.cs | 103 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 2 + 8 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs create mode 100644 OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs create mode 100644 OrleansDashboard/Implementation/GrainInteractionFilter.cs create mode 100644 OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs diff --git a/OrleansDashboard.Core/IDashboardGrain.cs b/OrleansDashboard.Core/IDashboardGrain.cs index 979160dd..6ebee32a 100644 --- a/OrleansDashboard.Core/IDashboardGrain.cs +++ b/OrleansDashboard.Core/IDashboardGrain.cs @@ -14,6 +14,9 @@ public interface IDashboardGrain : IGrainWithIntegerKey [OneWay] Task SubmitTracing(string siloAddress, Immutable grainCallTime); + + [OneWay] + Task SubmitGrainInteraction(string interactionsGraph); Task> GetCounters(); @@ -24,5 +27,7 @@ public interface IDashboardGrain : IGrainWithIntegerKey Task>> GetSiloTracing(string address); Task>> TopGrainMethods(); + + Task GetInteractionsGraph(); } } \ No newline at end of file diff --git a/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs b/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs new file mode 100644 index 00000000..9931ec0e --- /dev/null +++ b/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Orleans; +using Orleans.Concurrency; +using OrleansDashboard.Model; + +namespace OrleansDashboard.Metrics +{ + public interface IInteractionProfiler : IGrainWithIntegerKey + { + [OneWay] + Task Track(GrainInteractionInfoEntry entry); + } +} diff --git a/OrleansDashboard.Core/Model/DashboardCounters.cs b/OrleansDashboard.Core/Model/DashboardCounters.cs index f4046788..3bb069c0 100644 --- a/OrleansDashboard.Core/Model/DashboardCounters.cs +++ b/OrleansDashboard.Core/Model/DashboardCounters.cs @@ -26,5 +26,6 @@ public DashboardCounters() public SimpleGrainStatisticCounter[] SimpleGrainStats { get; set; } public int TotalActivationCount { get; set; } public ImmutableQueue TotalActivationCountHistory { get; set; } + public string InteractionsGraph { get; set; } } } \ No newline at end of file diff --git a/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs b/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs new file mode 100644 index 00000000..d5c856a0 --- /dev/null +++ b/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs @@ -0,0 +1,15 @@ +using System; + +namespace OrleansDashboard.Model +{ + [Serializable] + public class GrainInteractionInfoEntry + { + public string Grain { get; set; } + public string TargetGrain { get; set; } + public string Method { get; set; } + public uint Count { get; set; } = 1; + + public string Key => Grain + ":" + (TargetGrain ?? string.Empty) + ":" + Method; + } +} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/GrainInteractionFilter.cs b/OrleansDashboard/Implementation/GrainInteractionFilter.cs new file mode 100644 index 00000000..eaca74d1 --- /dev/null +++ b/OrleansDashboard/Implementation/GrainInteractionFilter.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Orleans; +using Orleans.Runtime; +using OrleansDashboard.Metrics; +using OrleansDashboard.Model; + +namespace OrleansDashboard.Implementation +{ + public sealed class GrainInteractionFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter + { + private readonly IGrainFactory grainFactory; + private IInteractionProfiler grainInteractionProfiler; + + public GrainInteractionFilter(IGrainFactory grainFactory) + { + this.grainFactory = grainFactory; + } + + public async Task Invoke(IIncomingGrainCallContext context) + { + try + { + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + await context.Invoke(); + } + finally + { + await TrackEndInvoke(); + } + } + + public async Task Invoke(IOutgoingGrainCallContext context) + { + try + { + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + await context.Invoke(); + } + finally + { + await TrackEndInvoke(); + } + } + + private Stack GetCallStack() + { + return RequestContext.Get(nameof(GrainInteractionFilter)) as Stack ?? new Stack(); + } + + private void SaveCallStack(Stack stack) + { + if (stack.Count == 0) + { + RequestContext.Remove(nameof(GrainInteractionFilter)); + } + else + { + RequestContext.Set(nameof(GrainInteractionFilter), stack); + } + } + + private void TrackBeginInvoke(string grain, string method) + { + var stack = GetCallStack(); + if (stack.TryPeek(out var info)) + { + info.TargetGrain = grain; + } + + stack.Push(new GrainInteractionInfoEntry + { + Grain = grain, + Method = method + }); + SaveCallStack(stack); + } + + private async Task TrackEndInvoke() + { + var stack = GetCallStack(); + var info = stack.Pop(); + + grainInteractionProfiler ??= grainFactory.GetGrain(0); + await grainInteractionProfiler.Track(info); + SaveCallStack(stack); + } + + private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + + private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + } +} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/Grains/DashboardGrain.cs b/OrleansDashboard/Implementation/Grains/DashboardGrain.cs index 6680740f..a6bb5ae6 100644 --- a/OrleansDashboard/Implementation/Grains/DashboardGrain.cs +++ b/OrleansDashboard/Implementation/Grains/DashboardGrain.cs @@ -113,7 +113,7 @@ public override Task OnActivateAsync() return base.OnActivateAsync(); } - + public async Task> GetCounters() { await EnsureCountersAreUpToDate(); @@ -156,7 +156,12 @@ public async Task>> TopGrai { "errors", values.Where(x => x.ExceptionCount > 0 && x.Count > 0).OrderByDescending(x => x.ExceptionCount / x.Count).Take(numberOfResultsToReturn).ToArray() }, }.AsImmutable(); } - + + public Task GetInteractionsGraph() + { + return Task.FromResult(counters.InteractionsGraph); + } + public Task Init() { // just used to activate the grain @@ -169,5 +174,11 @@ public Task SubmitTracing(string siloAddress, Immutable g return Task.CompletedTask; } + + public Task SubmitGrainInteraction(string interactionsGraph) + { + counters.InteractionsGraph = interactionsGraph; + return Task.CompletedTask; + } } } diff --git a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs new file mode 100644 index 00000000..a1cd1b23 --- /dev/null +++ b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Orleans; +using Orleans.Concurrency; +using OrleansDashboard.Model; + +namespace OrleansDashboard.Metrics.Grains +{ + [Reentrant] + public class InteractionProfilerGrain : Grain, IInteractionProfiler + { + private const int DefaultTimerIntervalMs = 1000; // 1 second + private readonly Dictionary> interaction = new(); + private readonly DashboardOptions options; + private IDisposable timer; + + public InteractionProfilerGrain(IOptions options) + { + this.options = options.Value; + } + + public Task Track(GrainInteractionInfoEntry entry) + { + if (interaction.TryGetValue(entry.Grain, out var existing)) + { + if (existing.TryGetValue(entry.Key, out var existingEntry)) + { + existingEntry.Count++; + } + else + { + existing[entry.Key] = entry; + } + } + else + { + interaction.Add(entry.Grain, new Dictionary + { + [entry.Key] = entry + }); + } + + return Task.CompletedTask; + } + + public override async Task OnActivateAsync() + { + var updateInterval = TimeSpan.FromMilliseconds(Math.Max(options.CounterUpdateIntervalMs, DefaultTimerIntervalMs)); + + try + { + timer = RegisterTimer(x => CollectStatistics((bool)x), true, updateInterval, updateInterval); + } + catch (InvalidOperationException) + { + Debug.WriteLine("Not running in Orleans runtime"); + } + + await base.OnActivateAsync(); + } + + private async Task CollectStatistics(bool canDeactivate) + { + var dashboardGrain = GrainFactory.GetGrain(0); + try + { + await dashboardGrain.SubmitGrainInteraction(BuildGraph()); + } + catch (Exception) + { + // we can't get the silo stats, it's probably dead, so kill the grain + if (canDeactivate) + { + timer?.Dispose(); + timer = null; + + DeactivateOnIdle(); + } + } + } + + private string BuildGraph() + { + var content = string.Join("\n ", interaction.Values.SelectMany(s => s) + /*.Where(w=>!string.IsNullOrEmpty(w.To))*/ + .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain} [ label = \"{s.Value.Method}\" ];")); + + var graphCode = @$" +digraph finite_state_machine {{ +rankdir=LR; +node [shape = doublecircle]; {interaction.Values.SelectMany(s => s).GroupBy(w => w.Value.Grain).OrderBy(s => s.Count()).First().Key}; +node [shape = circle]; + {content} + +}}"; + return graphCode; + } + } +} \ No newline at end of file diff --git a/OrleansDashboard/ServiceCollectionExtensions.cs b/OrleansDashboard/ServiceCollectionExtensions.cs index a0adbf16..539d70b3 100644 --- a/OrleansDashboard/ServiceCollectionExtensions.cs +++ b/OrleansDashboard/ServiceCollectionExtensions.cs @@ -26,6 +26,8 @@ public static ISiloHostBuilder UseDashboard(this ISiloHostBuilder builder, { builder.ConfigureApplicationParts(parts => parts.AddDashboardParts()); builder.ConfigureServices(services => services.AddDashboard(configurator)); + builder.AddIncomingGrainCallFilter(); + builder.AddOutgoingGrainCallFilter(); builder.AddStartupTask(); return builder; From 359406dcef9eb637d1fdf69f5f1268f9928f00ba Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 11 Feb 2022 16:49:54 +0100 Subject: [PATCH 2/9] Client --- OrleansDashboard.Core/DashboardClient.cs | 5 +++++ OrleansDashboard.Core/IDashboardClient.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/OrleansDashboard.Core/DashboardClient.cs b/OrleansDashboard.Core/DashboardClient.cs index 06981d1a..3f113545 100644 --- a/OrleansDashboard.Core/DashboardClient.cs +++ b/OrleansDashboard.Core/DashboardClient.cs @@ -68,5 +68,10 @@ public async Task>> TopGrai { return await dashboardGrain.TopGrainMethods().ConfigureAwait(false); } + + public async Task GetInteractionsGraph() + { + return await dashboardGrain.GetInteractionsGraph().ConfigureAwait(false); + } } } diff --git a/OrleansDashboard.Core/IDashboardClient.cs b/OrleansDashboard.Core/IDashboardClient.cs index e946cd73..68b5eab3 100644 --- a/OrleansDashboard.Core/IDashboardClient.cs +++ b/OrleansDashboard.Core/IDashboardClient.cs @@ -18,5 +18,6 @@ public interface IDashboardClient Task> GetCounters(string siloAddress); Task>>> GrainStats(string grainName); Task>> TopGrainMethods(); + Task GetInteractionsGraph(); } } \ No newline at end of file From ad407b599de734374428de9a2bc06730b9769a48 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Fri, 11 Feb 2022 21:55:22 +0100 Subject: [PATCH 3/9] new graph style --- .../Grains/InteractionProfilerGrain.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs index a1cd1b23..3334ea99 100644 --- a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs +++ b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs @@ -87,15 +87,21 @@ private string BuildGraph() { var content = string.Join("\n ", interaction.Values.SelectMany(s => s) /*.Where(w=>!string.IsNullOrEmpty(w.To))*/ - .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain} [ label = \"{s.Value.Method}\" ];")); + .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain+"_self"} [ label = \"{s.Value.Method}\", color=\"0.650 0.700 0.700\" ];")); + var colors = string.Join("\n", interaction.Values.SelectMany(s => s) + .Select(s => s.Value.Grain) + .Distinct() + .Select(s => $"{s} [color=\"0.628 0.227 1.000\"];")); + var graphCode = @$" digraph finite_state_machine {{ rankdir=LR; -node [shape = doublecircle]; {interaction.Values.SelectMany(s => s).GroupBy(w => w.Value.Grain).OrderBy(s => s.Count()).First().Key}; -node [shape = circle]; +ratio = fill; +node [style=filled]; {content} +{colors} }}"; return graphCode; } From 4992ac2acef93dd470cd2f7b3f585e595a092834 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Wed, 16 Feb 2022 12:01:50 +0100 Subject: [PATCH 4/9] GrainProfilerFilter --- .../Implementation/GrainInteractionFilter.cs | 101 ------------- .../Implementation/GrainProfilerFilter.cs | 135 ++++++++++++++++-- .../ServiceCollectionExtensions.cs | 3 +- Tests/TestGrains/TestCalls.cs | 3 +- Tests/TestGrains/TestGrain.cs | 8 ++ 5 files changed, 137 insertions(+), 113 deletions(-) delete mode 100644 OrleansDashboard/Implementation/GrainInteractionFilter.cs diff --git a/OrleansDashboard/Implementation/GrainInteractionFilter.cs b/OrleansDashboard/Implementation/GrainInteractionFilter.cs deleted file mode 100644 index eaca74d1..00000000 --- a/OrleansDashboard/Implementation/GrainInteractionFilter.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Orleans; -using Orleans.Runtime; -using OrleansDashboard.Metrics; -using OrleansDashboard.Model; - -namespace OrleansDashboard.Implementation -{ - public sealed class GrainInteractionFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter - { - private readonly IGrainFactory grainFactory; - private IInteractionProfiler grainInteractionProfiler; - - public GrainInteractionFilter(IGrainFactory grainFactory) - { - this.grainFactory = grainFactory; - } - - public async Task Invoke(IIncomingGrainCallContext context) - { - try - { - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); - await context.Invoke(); - } - finally - { - await TrackEndInvoke(); - } - } - - public async Task Invoke(IOutgoingGrainCallContext context) - { - try - { - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); - await context.Invoke(); - } - finally - { - await TrackEndInvoke(); - } - } - - private Stack GetCallStack() - { - return RequestContext.Get(nameof(GrainInteractionFilter)) as Stack ?? new Stack(); - } - - private void SaveCallStack(Stack stack) - { - if (stack.Count == 0) - { - RequestContext.Remove(nameof(GrainInteractionFilter)); - } - else - { - RequestContext.Set(nameof(GrainInteractionFilter), stack); - } - } - - private void TrackBeginInvoke(string grain, string method) - { - var stack = GetCallStack(); - if (stack.TryPeek(out var info)) - { - info.TargetGrain = grain; - } - - stack.Push(new GrainInteractionInfoEntry - { - Grain = grain, - Method = method - }); - SaveCallStack(stack); - } - - private async Task TrackEndInvoke() - { - var stack = GetCallStack(); - var info = stack.Pop(); - - grainInteractionProfiler ??= grainFactory.GetGrain(0); - await grainInteractionProfiler.Track(info); - SaveCallStack(stack); - } - - private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } - - private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } - } -} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/GrainProfilerFilter.cs b/OrleansDashboard/Implementation/GrainProfilerFilter.cs index fcab5a21..1dcaae4b 100644 --- a/OrleansDashboard/Implementation/GrainProfilerFilter.cs +++ b/OrleansDashboard/Implementation/GrainProfilerFilter.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans; +using Orleans.Runtime; +using OrleansDashboard.Implementation; +using OrleansDashboard.Model; namespace OrleansDashboard.Metrics { - public class GrainProfilerFilter : IIncomingGrainCallFilter + public class GrainProfilerFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter { public delegate string GrainMethodFormatterDelegate(IIncomingGrainCallContext callContext); @@ -38,13 +43,16 @@ public GrainProfilerFilter(IGrainProfiler profiler, ILogger public async Task Invoke(IIncomingGrainCallContext context) { - if (ShouldSkipProfiling(context)) + var info = GetGrainTypeAndMethodInfo(context); + if (ShouldSkipProfiling(info.GrainType, info.GrainMethodInfo)) { await context.Invoke(); return; } var stopwatch = Stopwatch.StartNew(); + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); try { @@ -57,7 +65,49 @@ public async Task Invoke(IIncomingGrainCallContext context) Track(context, stopwatch, true); throw; } + finally + { + await TrackEndInvoke(); + } } + + public async Task Invoke(IOutgoingGrainCallContext context) + { + var info = GetGrainTypeAndMethodInfo(context); + if (ShouldSkipProfiling(info.GrainType, info.GrainMethodInfo)) + { + await context.Invoke(); + return; + } + + var stopwatch = Stopwatch.StartNew(); + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + + try + { + await context.Invoke(); + + //Track(context, stopwatch, false); + } + catch (Exception) + { + //Track(context, stopwatch, true); + throw; + } + finally + { + await TrackEndInvoke(); + } + } + + + + + + + + private void Track(IIncomingGrainCallContext context, Stopwatch stopwatch, bool isException) { @@ -77,24 +127,45 @@ private void Track(IIncomingGrainCallContext context, Stopwatch stopwatch, bool } } - private bool ShouldSkipProfiling(IIncomingGrainCallContext context) + private void TrackBeginInvoke(string grain, string method) { - var grainMethod = context.ImplementationMethod; + var stack = GetCallStack(); + if (stack.TryPeek(out var info)) + { + info.TargetGrain = grain; + } - if (grainMethod == null) + stack.Push(new GrainInteractionInfoEntry + { + Grain = grain, + Method = method + }); + SaveCallStack(stack); + } + + private async Task TrackEndInvoke() + { + var stack = GetCallStack(); + var info = stack.Pop(); + SaveCallStack(stack); + } + + private bool ShouldSkipProfiling(Type GrainType, MethodInfo GrainMethodInfo) + { + if (GrainMethodInfo == null) { return false; } - if (!shouldSkipCache.TryGetValue(grainMethod, out var shouldSkip)) + if (!shouldSkipCache.TryGetValue(GrainMethodInfo, out var shouldSkip)) { try { - var grainType = context.Grain.GetType(); + var grainType = GrainType; shouldSkip = grainType.GetCustomAttribute() != null || - grainMethod.GetCustomAttribute() != null; + GrainMethodInfo.GetCustomAttribute() != null; } catch (Exception ex) { @@ -103,10 +174,56 @@ private bool ShouldSkipProfiling(IIncomingGrainCallContext context) shouldSkip = false; } - shouldSkipCache.TryAdd(grainMethod, shouldSkip); + shouldSkipCache.TryAdd(GrainMethodInfo, shouldSkip); } return shouldSkip; } + + + + ///// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Stack GetCallStack() + { + return RequestContext.Get(nameof(GrainProfilerFilter)) as Stack ?? new Stack(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SaveCallStack(Stack stack) + { + if (stack.Count == 0) + { + RequestContext.Remove(nameof(GrainProfilerFilter)); + } + else + { + RequestContext.Set(nameof(GrainProfilerFilter), stack); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IIncomingGrainCallContext context) + { + return (context.Grain.GetType(), context.ImplementationMethod); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IOutgoingGrainCallContext context) + { + return (context.Grain.GetType(), context.InterfaceMethod); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } } } diff --git a/OrleansDashboard/ServiceCollectionExtensions.cs b/OrleansDashboard/ServiceCollectionExtensions.cs index 539d70b3..592506ec 100644 --- a/OrleansDashboard/ServiceCollectionExtensions.cs +++ b/OrleansDashboard/ServiceCollectionExtensions.cs @@ -26,8 +26,6 @@ public static ISiloHostBuilder UseDashboard(this ISiloHostBuilder builder, { builder.ConfigureApplicationParts(parts => parts.AddDashboardParts()); builder.ConfigureServices(services => services.AddDashboard(configurator)); - builder.AddIncomingGrainCallFilter(); - builder.AddOutgoingGrainCallFilter(); builder.AddStartupTask(); return builder; @@ -56,6 +54,7 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, services.AddSingleton(); services.AddSingleton(c => (ILifecycleParticipant)c.GetRequiredService()); services.AddSingleton(); + services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton(c => diff --git a/Tests/TestGrains/TestCalls.cs b/Tests/TestGrains/TestCalls.cs index 33a4ff21..34e523f8 100644 --- a/Tests/TestGrains/TestCalls.cs +++ b/Tests/TestGrains/TestCalls.cs @@ -35,7 +35,8 @@ public static Task Make(IClusterClient client, CancellationTokenSource tokenSour await testGrain.ExampleMethod1(); await testGrain.ExampleMethod2(); - + await testGrain.ExampleMethod3(); + var genericClient = client.GetGrain>("foo"); await genericClient.Echo("hello world"); diff --git a/Tests/TestGrains/TestGrain.cs b/Tests/TestGrains/TestGrain.cs index ff519a96..51eaa710 100644 --- a/Tests/TestGrains/TestGrain.cs +++ b/Tests/TestGrains/TestGrain.cs @@ -10,6 +10,8 @@ public interface ITestGrain : IGrainWithIntegerKey Task ExampleMethod1(); Task ExampleMethod2(); + + Task ExampleMethod3(); } public class TestGrain : Grain, ITestGrain, IRemindable @@ -31,6 +33,12 @@ public Task ExampleMethod2() return Task.CompletedTask; } + public async Task ExampleMethod3() + { + await ExampleMethod1(); + await GrainFactory.GetGrain(100).Notify("test"); + } + public override async Task OnActivateAsync() { await RegisterOrUpdateReminder("Frequent", TimeSpan.Zero, TimeSpan.FromMinutes(1)); From 8347a5684641049e7f2e7f13785734fcaa6ac73e Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sun, 23 Oct 2022 11:22:20 +0200 Subject: [PATCH 5/9] Revert "GrainProfilerFilter" This reverts commit 4992ac2acef93dd470cd2f7b3f585e595a092834. --- .../Implementation/GrainInteractionFilter.cs | 101 +++++++++++++ .../Implementation/GrainProfilerFilter.cs | 135 ++---------------- .../ServiceCollectionExtensions.cs | 3 +- Tests/TestGrains/TestCalls.cs | 3 +- Tests/TestGrains/TestGrain.cs | 8 -- 5 files changed, 113 insertions(+), 137 deletions(-) create mode 100644 OrleansDashboard/Implementation/GrainInteractionFilter.cs diff --git a/OrleansDashboard/Implementation/GrainInteractionFilter.cs b/OrleansDashboard/Implementation/GrainInteractionFilter.cs new file mode 100644 index 00000000..eaca74d1 --- /dev/null +++ b/OrleansDashboard/Implementation/GrainInteractionFilter.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Orleans; +using Orleans.Runtime; +using OrleansDashboard.Metrics; +using OrleansDashboard.Model; + +namespace OrleansDashboard.Implementation +{ + public sealed class GrainInteractionFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter + { + private readonly IGrainFactory grainFactory; + private IInteractionProfiler grainInteractionProfiler; + + public GrainInteractionFilter(IGrainFactory grainFactory) + { + this.grainFactory = grainFactory; + } + + public async Task Invoke(IIncomingGrainCallContext context) + { + try + { + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + await context.Invoke(); + } + finally + { + await TrackEndInvoke(); + } + } + + public async Task Invoke(IOutgoingGrainCallContext context) + { + try + { + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + await context.Invoke(); + } + finally + { + await TrackEndInvoke(); + } + } + + private Stack GetCallStack() + { + return RequestContext.Get(nameof(GrainInteractionFilter)) as Stack ?? new Stack(); + } + + private void SaveCallStack(Stack stack) + { + if (stack.Count == 0) + { + RequestContext.Remove(nameof(GrainInteractionFilter)); + } + else + { + RequestContext.Set(nameof(GrainInteractionFilter), stack); + } + } + + private void TrackBeginInvoke(string grain, string method) + { + var stack = GetCallStack(); + if (stack.TryPeek(out var info)) + { + info.TargetGrain = grain; + } + + stack.Push(new GrainInteractionInfoEntry + { + Grain = grain, + Method = method + }); + SaveCallStack(stack); + } + + private async Task TrackEndInvoke() + { + var stack = GetCallStack(); + var info = stack.Pop(); + + grainInteractionProfiler ??= grainFactory.GetGrain(0); + await grainInteractionProfiler.Track(info); + SaveCallStack(stack); + } + + private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + + private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + } +} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/GrainProfilerFilter.cs b/OrleansDashboard/Implementation/GrainProfilerFilter.cs index 1dcaae4b..fcab5a21 100644 --- a/OrleansDashboard/Implementation/GrainProfilerFilter.cs +++ b/OrleansDashboard/Implementation/GrainProfilerFilter.cs @@ -1,19 +1,14 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.Reflection; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Orleans; -using Orleans.Runtime; -using OrleansDashboard.Implementation; -using OrleansDashboard.Model; namespace OrleansDashboard.Metrics { - public class GrainProfilerFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter + public class GrainProfilerFilter : IIncomingGrainCallFilter { public delegate string GrainMethodFormatterDelegate(IIncomingGrainCallContext callContext); @@ -43,16 +38,13 @@ public GrainProfilerFilter(IGrainProfiler profiler, ILogger public async Task Invoke(IIncomingGrainCallContext context) { - var info = GetGrainTypeAndMethodInfo(context); - if (ShouldSkipProfiling(info.GrainType, info.GrainMethodInfo)) + if (ShouldSkipProfiling(context)) { await context.Invoke(); return; } var stopwatch = Stopwatch.StartNew(); - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); try { @@ -65,49 +57,7 @@ public async Task Invoke(IIncomingGrainCallContext context) Track(context, stopwatch, true); throw; } - finally - { - await TrackEndInvoke(); - } } - - public async Task Invoke(IOutgoingGrainCallContext context) - { - var info = GetGrainTypeAndMethodInfo(context); - if (ShouldSkipProfiling(info.GrainType, info.GrainMethodInfo)) - { - await context.Invoke(); - return; - } - - var stopwatch = Stopwatch.StartNew(); - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); - - try - { - await context.Invoke(); - - //Track(context, stopwatch, false); - } - catch (Exception) - { - //Track(context, stopwatch, true); - throw; - } - finally - { - await TrackEndInvoke(); - } - } - - - - - - - - private void Track(IIncomingGrainCallContext context, Stopwatch stopwatch, bool isException) { @@ -127,45 +77,24 @@ private void Track(IIncomingGrainCallContext context, Stopwatch stopwatch, bool } } - private void TrackBeginInvoke(string grain, string method) + private bool ShouldSkipProfiling(IIncomingGrainCallContext context) { - var stack = GetCallStack(); - if (stack.TryPeek(out var info)) - { - info.TargetGrain = grain; - } + var grainMethod = context.ImplementationMethod; - stack.Push(new GrainInteractionInfoEntry - { - Grain = grain, - Method = method - }); - SaveCallStack(stack); - } - - private async Task TrackEndInvoke() - { - var stack = GetCallStack(); - var info = stack.Pop(); - SaveCallStack(stack); - } - - private bool ShouldSkipProfiling(Type GrainType, MethodInfo GrainMethodInfo) - { - if (GrainMethodInfo == null) + if (grainMethod == null) { return false; } - if (!shouldSkipCache.TryGetValue(GrainMethodInfo, out var shouldSkip)) + if (!shouldSkipCache.TryGetValue(grainMethod, out var shouldSkip)) { try { - var grainType = GrainType; + var grainType = context.Grain.GetType(); shouldSkip = grainType.GetCustomAttribute() != null || - GrainMethodInfo.GetCustomAttribute() != null; + grainMethod.GetCustomAttribute() != null; } catch (Exception ex) { @@ -174,56 +103,10 @@ private bool ShouldSkipProfiling(Type GrainType, MethodInfo GrainMethodInfo) shouldSkip = false; } - shouldSkipCache.TryAdd(GrainMethodInfo, shouldSkip); + shouldSkipCache.TryAdd(grainMethod, shouldSkip); } return shouldSkip; } - - - - ///// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Stack GetCallStack() - { - return RequestContext.Get(nameof(GrainProfilerFilter)) as Stack ?? new Stack(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SaveCallStack(Stack stack) - { - if (stack.Count == 0) - { - RequestContext.Remove(nameof(GrainProfilerFilter)); - } - else - { - RequestContext.Set(nameof(GrainProfilerFilter), stack); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IIncomingGrainCallContext context) - { - return (context.Grain.GetType(), context.ImplementationMethod); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IOutgoingGrainCallContext context) - { - return (context.Grain.GetType(), context.InterfaceMethod); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } } } diff --git a/OrleansDashboard/ServiceCollectionExtensions.cs b/OrleansDashboard/ServiceCollectionExtensions.cs index 592506ec..539d70b3 100644 --- a/OrleansDashboard/ServiceCollectionExtensions.cs +++ b/OrleansDashboard/ServiceCollectionExtensions.cs @@ -26,6 +26,8 @@ public static ISiloHostBuilder UseDashboard(this ISiloHostBuilder builder, { builder.ConfigureApplicationParts(parts => parts.AddDashboardParts()); builder.ConfigureServices(services => services.AddDashboard(configurator)); + builder.AddIncomingGrainCallFilter(); + builder.AddOutgoingGrainCallFilter(); builder.AddStartupTask(); return builder; @@ -54,7 +56,6 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, services.AddSingleton(); services.AddSingleton(c => (ILifecycleParticipant)c.GetRequiredService()); services.AddSingleton(); - services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton(c => diff --git a/Tests/TestGrains/TestCalls.cs b/Tests/TestGrains/TestCalls.cs index 34e523f8..33a4ff21 100644 --- a/Tests/TestGrains/TestCalls.cs +++ b/Tests/TestGrains/TestCalls.cs @@ -35,8 +35,7 @@ public static Task Make(IClusterClient client, CancellationTokenSource tokenSour await testGrain.ExampleMethod1(); await testGrain.ExampleMethod2(); - await testGrain.ExampleMethod3(); - + var genericClient = client.GetGrain>("foo"); await genericClient.Echo("hello world"); diff --git a/Tests/TestGrains/TestGrain.cs b/Tests/TestGrains/TestGrain.cs index 51eaa710..ff519a96 100644 --- a/Tests/TestGrains/TestGrain.cs +++ b/Tests/TestGrains/TestGrain.cs @@ -10,8 +10,6 @@ public interface ITestGrain : IGrainWithIntegerKey Task ExampleMethod1(); Task ExampleMethod2(); - - Task ExampleMethod3(); } public class TestGrain : Grain, ITestGrain, IRemindable @@ -33,12 +31,6 @@ public Task ExampleMethod2() return Task.CompletedTask; } - public async Task ExampleMethod3() - { - await ExampleMethod1(); - await GrainFactory.GetGrain(100).Notify("test"); - } - public override async Task OnActivateAsync() { await RegisterOrUpdateReminder("Frequent", TimeSpan.Zero, TimeSpan.FromMinutes(1)); From b9f8836770cc9531029f78b524a37c66ca5cfb1c Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sun, 23 Oct 2022 11:22:27 +0200 Subject: [PATCH 6/9] Revert "new graph style" This reverts commit ad407b599de734374428de9a2bc06730b9769a48. --- .../Grains/InteractionProfilerGrain.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs index 3334ea99..a1cd1b23 100644 --- a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs +++ b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs @@ -87,21 +87,15 @@ private string BuildGraph() { var content = string.Join("\n ", interaction.Values.SelectMany(s => s) /*.Where(w=>!string.IsNullOrEmpty(w.To))*/ - .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain+"_self"} [ label = \"{s.Value.Method}\", color=\"0.650 0.700 0.700\" ];")); + .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain} [ label = \"{s.Value.Method}\" ];")); - var colors = string.Join("\n", interaction.Values.SelectMany(s => s) - .Select(s => s.Value.Grain) - .Distinct() - .Select(s => $"{s} [color=\"0.628 0.227 1.000\"];")); - var graphCode = @$" digraph finite_state_machine {{ rankdir=LR; -ratio = fill; -node [style=filled]; +node [shape = doublecircle]; {interaction.Values.SelectMany(s => s).GroupBy(w => w.Value.Grain).OrderBy(s => s.Count()).First().Key}; +node [shape = circle]; {content} -{colors} }}"; return graphCode; } From ffd0004a590c51d91cb9f667b3840c6e5bbdba25 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sun, 23 Oct 2022 11:22:34 +0200 Subject: [PATCH 7/9] Revert "Client" This reverts commit 359406dcef9eb637d1fdf69f5f1268f9928f00ba. --- OrleansDashboard.Core/DashboardClient.cs | 5 ----- OrleansDashboard.Core/IDashboardClient.cs | 1 - 2 files changed, 6 deletions(-) diff --git a/OrleansDashboard.Core/DashboardClient.cs b/OrleansDashboard.Core/DashboardClient.cs index 3f113545..06981d1a 100644 --- a/OrleansDashboard.Core/DashboardClient.cs +++ b/OrleansDashboard.Core/DashboardClient.cs @@ -68,10 +68,5 @@ public async Task>> TopGrai { return await dashboardGrain.TopGrainMethods().ConfigureAwait(false); } - - public async Task GetInteractionsGraph() - { - return await dashboardGrain.GetInteractionsGraph().ConfigureAwait(false); - } } } diff --git a/OrleansDashboard.Core/IDashboardClient.cs b/OrleansDashboard.Core/IDashboardClient.cs index 68b5eab3..e946cd73 100644 --- a/OrleansDashboard.Core/IDashboardClient.cs +++ b/OrleansDashboard.Core/IDashboardClient.cs @@ -18,6 +18,5 @@ public interface IDashboardClient Task> GetCounters(string siloAddress); Task>>> GrainStats(string grainName); Task>> TopGrainMethods(); - Task GetInteractionsGraph(); } } \ No newline at end of file From 11967cbdf403f975b0eeef906fe6921c4b3b3886 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sun, 23 Oct 2022 11:22:43 +0200 Subject: [PATCH 8/9] Revert "InteractionProfilerGrain" This reverts commit c321edf06dde04f61f4584249742dd9cd929f82d. --- OrleansDashboard.Core/IDashboardGrain.cs | 5 - .../Metrics/IGrainInteractionProfiler.cs | 15 --- .../Model/DashboardCounters.cs | 1 - .../Model/GrainInteractionInfoEntry.cs | 15 --- .../Implementation/GrainInteractionFilter.cs | 101 ----------------- .../Implementation/Grains/DashboardGrain.cs | 15 +-- .../Grains/InteractionProfilerGrain.cs | 103 ------------------ .../ServiceCollectionExtensions.cs | 2 - 8 files changed, 2 insertions(+), 255 deletions(-) delete mode 100644 OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs delete mode 100644 OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs delete mode 100644 OrleansDashboard/Implementation/GrainInteractionFilter.cs delete mode 100644 OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs diff --git a/OrleansDashboard.Core/IDashboardGrain.cs b/OrleansDashboard.Core/IDashboardGrain.cs index 6ebee32a..979160dd 100644 --- a/OrleansDashboard.Core/IDashboardGrain.cs +++ b/OrleansDashboard.Core/IDashboardGrain.cs @@ -14,9 +14,6 @@ public interface IDashboardGrain : IGrainWithIntegerKey [OneWay] Task SubmitTracing(string siloAddress, Immutable grainCallTime); - - [OneWay] - Task SubmitGrainInteraction(string interactionsGraph); Task> GetCounters(); @@ -27,7 +24,5 @@ public interface IDashboardGrain : IGrainWithIntegerKey Task>> GetSiloTracing(string address); Task>> TopGrainMethods(); - - Task GetInteractionsGraph(); } } \ No newline at end of file diff --git a/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs b/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs deleted file mode 100644 index 9931ec0e..00000000 --- a/OrleansDashboard.Core/Metrics/IGrainInteractionProfiler.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Orleans; -using Orleans.Concurrency; -using OrleansDashboard.Model; - -namespace OrleansDashboard.Metrics -{ - public interface IInteractionProfiler : IGrainWithIntegerKey - { - [OneWay] - Task Track(GrainInteractionInfoEntry entry); - } -} diff --git a/OrleansDashboard.Core/Model/DashboardCounters.cs b/OrleansDashboard.Core/Model/DashboardCounters.cs index 3bb069c0..f4046788 100644 --- a/OrleansDashboard.Core/Model/DashboardCounters.cs +++ b/OrleansDashboard.Core/Model/DashboardCounters.cs @@ -26,6 +26,5 @@ public DashboardCounters() public SimpleGrainStatisticCounter[] SimpleGrainStats { get; set; } public int TotalActivationCount { get; set; } public ImmutableQueue TotalActivationCountHistory { get; set; } - public string InteractionsGraph { get; set; } } } \ No newline at end of file diff --git a/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs b/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs deleted file mode 100644 index d5c856a0..00000000 --- a/OrleansDashboard.Core/Model/GrainInteractionInfoEntry.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace OrleansDashboard.Model -{ - [Serializable] - public class GrainInteractionInfoEntry - { - public string Grain { get; set; } - public string TargetGrain { get; set; } - public string Method { get; set; } - public uint Count { get; set; } = 1; - - public string Key => Grain + ":" + (TargetGrain ?? string.Empty) + ":" + Method; - } -} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/GrainInteractionFilter.cs b/OrleansDashboard/Implementation/GrainInteractionFilter.cs deleted file mode 100644 index eaca74d1..00000000 --- a/OrleansDashboard/Implementation/GrainInteractionFilter.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Orleans; -using Orleans.Runtime; -using OrleansDashboard.Metrics; -using OrleansDashboard.Model; - -namespace OrleansDashboard.Implementation -{ - public sealed class GrainInteractionFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter - { - private readonly IGrainFactory grainFactory; - private IInteractionProfiler grainInteractionProfiler; - - public GrainInteractionFilter(IGrainFactory grainFactory) - { - this.grainFactory = grainFactory; - } - - public async Task Invoke(IIncomingGrainCallContext context) - { - try - { - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); - await context.Invoke(); - } - finally - { - await TrackEndInvoke(); - } - } - - public async Task Invoke(IOutgoingGrainCallContext context) - { - try - { - var call = GetGrainMethod(context); - TrackBeginInvoke(call.Grain, call.Method); - await context.Invoke(); - } - finally - { - await TrackEndInvoke(); - } - } - - private Stack GetCallStack() - { - return RequestContext.Get(nameof(GrainInteractionFilter)) as Stack ?? new Stack(); - } - - private void SaveCallStack(Stack stack) - { - if (stack.Count == 0) - { - RequestContext.Remove(nameof(GrainInteractionFilter)); - } - else - { - RequestContext.Set(nameof(GrainInteractionFilter), stack); - } - } - - private void TrackBeginInvoke(string grain, string method) - { - var stack = GetCallStack(); - if (stack.TryPeek(out var info)) - { - info.TargetGrain = grain; - } - - stack.Push(new GrainInteractionInfoEntry - { - Grain = grain, - Method = method - }); - SaveCallStack(stack); - } - - private async Task TrackEndInvoke() - { - var stack = GetCallStack(); - var info = stack.Pop(); - - grainInteractionProfiler ??= grainFactory.GetGrain(0); - await grainInteractionProfiler.Track(info); - SaveCallStack(stack); - } - - private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } - - private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) - { - return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); - } - } -} \ No newline at end of file diff --git a/OrleansDashboard/Implementation/Grains/DashboardGrain.cs b/OrleansDashboard/Implementation/Grains/DashboardGrain.cs index a6bb5ae6..6680740f 100644 --- a/OrleansDashboard/Implementation/Grains/DashboardGrain.cs +++ b/OrleansDashboard/Implementation/Grains/DashboardGrain.cs @@ -113,7 +113,7 @@ public override Task OnActivateAsync() return base.OnActivateAsync(); } - + public async Task> GetCounters() { await EnsureCountersAreUpToDate(); @@ -156,12 +156,7 @@ public async Task>> TopGrai { "errors", values.Where(x => x.ExceptionCount > 0 && x.Count > 0).OrderByDescending(x => x.ExceptionCount / x.Count).Take(numberOfResultsToReturn).ToArray() }, }.AsImmutable(); } - - public Task GetInteractionsGraph() - { - return Task.FromResult(counters.InteractionsGraph); - } - + public Task Init() { // just used to activate the grain @@ -174,11 +169,5 @@ public Task SubmitTracing(string siloAddress, Immutable g return Task.CompletedTask; } - - public Task SubmitGrainInteraction(string interactionsGraph) - { - counters.InteractionsGraph = interactionsGraph; - return Task.CompletedTask; - } } } diff --git a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs b/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs deleted file mode 100644 index a1cd1b23..00000000 --- a/OrleansDashboard/Implementation/Grains/InteractionProfilerGrain.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Orleans; -using Orleans.Concurrency; -using OrleansDashboard.Model; - -namespace OrleansDashboard.Metrics.Grains -{ - [Reentrant] - public class InteractionProfilerGrain : Grain, IInteractionProfiler - { - private const int DefaultTimerIntervalMs = 1000; // 1 second - private readonly Dictionary> interaction = new(); - private readonly DashboardOptions options; - private IDisposable timer; - - public InteractionProfilerGrain(IOptions options) - { - this.options = options.Value; - } - - public Task Track(GrainInteractionInfoEntry entry) - { - if (interaction.TryGetValue(entry.Grain, out var existing)) - { - if (existing.TryGetValue(entry.Key, out var existingEntry)) - { - existingEntry.Count++; - } - else - { - existing[entry.Key] = entry; - } - } - else - { - interaction.Add(entry.Grain, new Dictionary - { - [entry.Key] = entry - }); - } - - return Task.CompletedTask; - } - - public override async Task OnActivateAsync() - { - var updateInterval = TimeSpan.FromMilliseconds(Math.Max(options.CounterUpdateIntervalMs, DefaultTimerIntervalMs)); - - try - { - timer = RegisterTimer(x => CollectStatistics((bool)x), true, updateInterval, updateInterval); - } - catch (InvalidOperationException) - { - Debug.WriteLine("Not running in Orleans runtime"); - } - - await base.OnActivateAsync(); - } - - private async Task CollectStatistics(bool canDeactivate) - { - var dashboardGrain = GrainFactory.GetGrain(0); - try - { - await dashboardGrain.SubmitGrainInteraction(BuildGraph()); - } - catch (Exception) - { - // we can't get the silo stats, it's probably dead, so kill the grain - if (canDeactivate) - { - timer?.Dispose(); - timer = null; - - DeactivateOnIdle(); - } - } - } - - private string BuildGraph() - { - var content = string.Join("\n ", interaction.Values.SelectMany(s => s) - /*.Where(w=>!string.IsNullOrEmpty(w.To))*/ - .Select(s => $"{s.Value.Grain} -> {s.Value.TargetGrain ?? s.Value.Grain} [ label = \"{s.Value.Method}\" ];")); - - var graphCode = @$" -digraph finite_state_machine {{ -rankdir=LR; -node [shape = doublecircle]; {interaction.Values.SelectMany(s => s).GroupBy(w => w.Value.Grain).OrderBy(s => s.Count()).First().Key}; -node [shape = circle]; - {content} - -}}"; - return graphCode; - } - } -} \ No newline at end of file diff --git a/OrleansDashboard/ServiceCollectionExtensions.cs b/OrleansDashboard/ServiceCollectionExtensions.cs index 539d70b3..a0adbf16 100644 --- a/OrleansDashboard/ServiceCollectionExtensions.cs +++ b/OrleansDashboard/ServiceCollectionExtensions.cs @@ -26,8 +26,6 @@ public static ISiloHostBuilder UseDashboard(this ISiloHostBuilder builder, { builder.ConfigureApplicationParts(parts => parts.AddDashboardParts()); builder.ConfigureServices(services => services.AddDashboard(configurator)); - builder.AddIncomingGrainCallFilter(); - builder.AddOutgoingGrainCallFilter(); builder.AddStartupTask(); return builder; From 291463e4bbebc689bc524ff3afe82270b0407132 Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Sun, 23 Oct 2022 17:55:46 +0200 Subject: [PATCH 9/9] filter --- .../Implementation/GrainCallProfilerFilter.cs | 217 ++++++++++++++++++ .../ServiceCollectionExtensions.cs | 4 + Tests/TestGrains/InteractionTestCalls.cs | 58 +++++ Tests/TestGrains/InteractionTestGrain.cs | 64 ++++++ Tests/TestHosts/TestHost/Program.cs | 1 + Tests/TestHosts/TestHostCohosted2/Program.cs | 2 +- 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 OrleansDashboard/Implementation/GrainCallProfilerFilter.cs create mode 100644 Tests/TestGrains/InteractionTestCalls.cs create mode 100644 Tests/TestGrains/InteractionTestGrain.cs diff --git a/OrleansDashboard/Implementation/GrainCallProfilerFilter.cs b/OrleansDashboard/Implementation/GrainCallProfilerFilter.cs new file mode 100644 index 00000000..4429c6cf --- /dev/null +++ b/OrleansDashboard/Implementation/GrainCallProfilerFilter.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Orleans; +using Orleans.Runtime; + +namespace OrleansDashboard.Metrics +{ + public class GrainCallProfilerFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter + { + private readonly IGrainProfiler profiler; + private readonly ILogger logger; + private readonly ConcurrentDictionary shouldSkipCache = new ConcurrentDictionary(); + + + + + public GrainCallProfilerFilter(IGrainProfiler profiler, ILogger logger) + { + this.profiler = profiler; + this.logger = logger; + } + + + private bool ShouldSkipProfiling(IGrainCallContext context) + { + var grainMethod = context.InterfaceMethod; + + if (grainMethod == null) + { + return false; + } + + if (!shouldSkipCache.TryGetValue(grainMethod, out var shouldSkip)) + { + try + { + var grainType = context.Grain.GetType(); + + shouldSkip = + grainType.GetCustomAttribute() != null || + grainMethod.GetCustomAttribute() != null; + } + catch (Exception ex) + { + logger.LogError(100003, ex, "error reading NoProfilingAttribute attribute for grain"); + + shouldSkip = false; + } + + shouldSkipCache.TryAdd(grainMethod, shouldSkip); + } + + return shouldSkip; + } + + public async Task Invoke(IIncomingGrainCallContext context) + { + if (ShouldSkipProfiling(context)) + { + await context.Invoke(); + return; + } + + if (IsDebgu(context.InterfaceMethod.Name)) + { + //in + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + } + + try + { + await context.Invoke(); + } + finally + { + if (IsDebgu(context.InterfaceMethod.Name)) + { + //in + TrackEndInvoke(); + } + + + } + } + + public async Task Invoke(IOutgoingGrainCallContext context) + { + if (ShouldSkipProfiling(context)) + { + await context.Invoke(); + return; + } + + + if (IsDebgu(context.InterfaceMethod.Name)) + { + //out + var call = GetGrainMethod(context); + TrackBeginInvoke(call.Grain, call.Method); + } + + try + { + await context.Invoke(); + } + finally + { + if (IsDebgu(context.InterfaceMethod.Name)) + { + //out + TrackEndInvoke(); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Stack GetCallStack() + { + return RequestContext.Get(nameof(GrainProfilerFilter)) as Stack ?? new Stack(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SaveCallStack(Stack stack) + { + if (stack.Count == 0) + { + RequestContext.Remove(nameof(GrainProfilerFilter)); + } + else + { + RequestContext.Set(nameof(GrainProfilerFilter), stack); + } + } + + private bool IsDebgu(string name) + { + if (name == "CallSecondInteractionTestGrain" || + name == "CallThirdInteractionTestGrain" || + name == "ITestGrain" || + name == "CallFirstInteractionTestGrain") + return true; + + return false; + } + + private void TrackBeginInvoke(string grain, string method) + { + var stack = GetCallStack(); + if (stack.TryPeek(out var info)) + { + info.TargetGrain = grain; + } + + stack.Push(new GrainInteractionInfoEntry + { + Grain = grain, + Method = method + }); + SaveCallStack(stack); + } + + private void TrackEndInvoke() + { + var stack = GetCallStack(); + var info = stack.Pop(); + + //grainInteractionProfiler ??= grainFactory.GetGrain(0); + //await grainInteractionProfiler.Track(info); + SaveCallStack(stack); + + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IIncomingGrainCallContext context) + { + return (context.Grain.GetType(), context.ImplementationMethod); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IOutgoingGrainCallContext context) + { + return (context.Grain.GetType(), context.InterfaceMethod); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context) + { + return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name); + } + } +} + +[Serializable] +public class GrainInteractionInfoEntry +{ + public string Grain { get; set; } + public string TargetGrain { get; set; } + public string Method { get; set; } + public uint Count { get; set; } = 1; + + public string Key => Grain + ":" + (TargetGrain ?? string.Empty) + ":" + Method; +} + diff --git a/OrleansDashboard/ServiceCollectionExtensions.cs b/OrleansDashboard/ServiceCollectionExtensions.cs index a0adbf16..8966a0d6 100644 --- a/OrleansDashboard/ServiceCollectionExtensions.cs +++ b/OrleansDashboard/ServiceCollectionExtensions.cs @@ -54,6 +54,10 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, services.AddSingleton(); services.AddSingleton(c => (ILifecycleParticipant)c.GetRequiredService()); services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.TryAddSingleton(); services.AddSingleton(c => diff --git a/Tests/TestGrains/InteractionTestCalls.cs b/Tests/TestGrains/InteractionTestCalls.cs new file mode 100644 index 00000000..2eb8e488 --- /dev/null +++ b/Tests/TestGrains/InteractionTestCalls.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Orleans; + +namespace TestGrains +{ + public static class InteractionTestCalls + { + public static Task Make(IClusterClient client, CancellationTokenSource tokenSource) + { + return Task.Run(async () => + { + var random = new Random(); + + while (!tokenSource.IsCancellationRequested) + { + try + { + // client => First => Second + + // client => Second => Third + // client => Second => Test + + // client => Third => Test + var rnd = random.Next(1, 3); + + switch (rnd) + { + case 1: + { + var testGrain = client.GetGrain(random.Next(100)); + await testGrain.CallFirstInteractionTestGrain(random.Next(100)); + break; + } + case 2: + { + var testGrain = client.GetGrain(random.Next(100)); + await testGrain.CallSecondInteractionTestGrain(random.Next(100)); + break; + } + case 3: + { + var testGrain = client.GetGrain(random.Next(100)); + await testGrain.CallThirdInteractionTestGrain(random.Next(100)); + break; + } + } + } + catch + { + // Grain might throw exception to test error rate. + } + } + }); + } + } +} \ No newline at end of file diff --git a/Tests/TestGrains/InteractionTestGrain.cs b/Tests/TestGrains/InteractionTestGrain.cs new file mode 100644 index 00000000..4c6f6478 --- /dev/null +++ b/Tests/TestGrains/InteractionTestGrain.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Orleans; +using Orleans.Runtime; + +namespace TestGrains +{ + public interface IFirstInteractionTestGrain : ITestGrain + { + Task CallFirstInteractionTestGrain(int id); + + Task CallSecondInteractionTestGrain(int id); + + Task CallThirdInteractionTestGrain(int id); + + Task CallTestGrain(int id); + } + + public interface ISecondInteractionTestGrain : IFirstInteractionTestGrain + { + } + + public interface IThirdInteractionTestGrain : IFirstInteractionTestGrain + { + } + + public class FirstInteractionTestGrain : TestGrain, IFirstInteractionTestGrain + { + + Random random = new Random(); + public Task CallFirstInteractionTestGrain(int id) + { + return GrainFactory.GetGrain(id).CallSecondInteractionTestGrain(id); + } + + public async Task CallSecondInteractionTestGrain(int id) + { + await GrainFactory.GetGrain(id).ExampleMethod1(); + await GrainFactory.GetGrain(id).ExampleMethod1(); + } + + public Task CallThirdInteractionTestGrain(int id) + { + return GrainFactory.GetGrain(id).ExampleMethod1(); + } + + public Task CallTestGrain(int id) + { + return GrainFactory.GetGrain(id).ExampleMethod1(); + } + } + + + + public class SecondInteractionTestGrain : FirstInteractionTestGrain, ISecondInteractionTestGrain + { + + } + + public class ThirdInteractionTestGrain : FirstInteractionTestGrain, IThirdInteractionTestGrain + { + + } +} \ No newline at end of file diff --git a/Tests/TestHosts/TestHost/Program.cs b/Tests/TestHosts/TestHost/Program.cs index c0e98939..20b49f05 100644 --- a/Tests/TestHosts/TestHost/Program.cs +++ b/Tests/TestHosts/TestHost/Program.cs @@ -64,6 +64,7 @@ public static void Main(string[] args) var cts = new CancellationTokenSource(); TestCalls.Make(client, cts); + InteractionTestCalls.Make(client, cts); Console.WriteLine("Press key to exit..."); Console.ReadLine(); diff --git a/Tests/TestHosts/TestHostCohosted2/Program.cs b/Tests/TestHosts/TestHostCohosted2/Program.cs index 6237dafc..29eee69e 100644 --- a/Tests/TestHosts/TestHostCohosted2/Program.cs +++ b/Tests/TestHosts/TestHostCohosted2/Program.cs @@ -22,7 +22,7 @@ static void Main(string[] args) int gatewayPort = 30000; - builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(siloAddress, siloPort)); + builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(siloAddress, siloPort)); builder.UseInMemoryReminderService(); builder.ConfigureEndpoints(siloAddress, siloPort, gatewayPort); builder.Configure(options =>